# Supports Organizer.rb
=begin rdoc

= 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.2.2
SU Version:: 4.0
Date:: 2014-05-16
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.0.0:: 2005-11-29
	* first version
* 2.0.0:: 2005-12-05
	* updated; planned improvements include specifying distance between copies, specifying a divisor
* 2.1.0:: 2005-12-07
	* implemented specifying distance between copies
	* fixed bug where groups would not copy/rotate properly
* 2.2.0:: 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
* 2.2.1:: 2014-05-09
	* removed the vector.flat_angle method
	* updated the SmustardToolbar reference from a global to a Smustard module constant
	* updated the Organizer reference from a global to a Smustard module constant
* 2.2.2:: 2014-05-16
	* version 3.0.4 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.
	

ToDoList::
	* Specify a divisor (done in Pro)

=end


module Smustard
	
	if ($submenu) # legacy programs creating a global
		Smustard::Submenu = $submenu unless Smustard.constants.include?("Submenu")
	else
		Smustard::Submenu = nil unless Smustard.constants.include?("Submenu")
	end
	

	if ($smustard_toolbar)	# legacy programs creating a global
		Smustard::Toolbar = $smustard_toolbar unless Smustard.constants.include?("Toolbar")
			
	else
		Smustard::Toolbar = UI::Toolbar.new("Smustard") unless Smustard.constants.include?("Toolbar")
			
	end
		
		

class PathCopy
	
	@@nCursor = 0
	@@type ||= "Spacing"
	@@count = 0
	@@spacing = 120.inch
	@@menuitem = nil
	
	@@cmd_pathcopy = UI::Command.new("PathCopy"){ Sketchup.active_model.select_tool Smustard::PathCopy.new }
	@@cmd_pathcopy.tooltip = "Copy group/component to nodes along a path"
	@@cmd_pathcopy.large_icon = "PathCopyLarge.png"
	@@cmd_pathcopy.small_icon = "PathCopySmall.png"

	
	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("pathcopyCursor.png", "Plugins/smustard_PathCopy")
			@@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
			#if value < 0 # divide path into this many segments
			#	value = (@path.length/(-(text.to_l)))
			#end
		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("PathCopy Component")
		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("PathCopy Group")
		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



	def PathCopy.build_ui()
		Smustard::Submenu ? (organizer = Smustard::Submenu) : (organizer = UI.menu("Plugins"))
		organizer.add_item(@@cmd_pathcopy)
		Smustard::Toolbar.add_item(@@cmd_pathcopy)
		
	end
	


end #class PathCopy

end #module Smustard



unless file_loaded?(__FILE__)
	Smustard::PathCopy.build_ui()
	file_loaded(__FILE__)
end