# Supports Organizer.rb
=begin rdoc
= Shape Bender
Copyright 2009, Chris Fullmer
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:: Chris Fullmer
Organization:: www.ChrisFullmer.com and distributed on www.sketchucation.com
Name:: Shape Bender
Version:: 0.55 Beta
SU Version:: I developed this on v7, but older versions should work.
Date:: April 21, 2010
Description:: This script will bend an existing group to match a curved line or series of connected lines.
Usage:: Create a shape to bend.  It must be a group or a component.  Then select a single horizontal (red axis) line to use as the base for bending.  Then select the curve that the shape will bend to match.  Wait for it to think, then it will show a grid and preview of where your object is going to transform to.  Press the UP Arrow key to toggle the orientation of the bend.
Requires:: Progressbar.rb from www.smustard.com
History::
0.1 Beta:: 2009-04-08
   * Original release.  I'm expecting plenty of bugs and feedback.
0.2 Beta:: 2009-04-09
   * Now will bend components (and still bends groups).
   * Works when the shape to bend is comprised of components and groups by exploding all the groups and components inside.
   * Does not explode the shape at any point (thats very good!)
   * Works with scaled and/or rotated components
   * Added line and arc highlighting to indicate when you move the mouse over an acceptable selection for the line or the arc
   * Fixed a bug where selecting a closed loop would hang the plugin.  Now it gives an error.
0.25 Beta:: 2009-04-10
   * Hit the down arrow key to change the start/end positions of the line.  The line needs to have its start point "to the left" of the endpoint...  If you notice weird behavior in how the shape is lining up to be bent, try hitting the down arrow key.
   * Up arrow key AND Home hey toggle the curve.  Downd arrow key AND End toggle the line now for added compatibility (for BTM :)
0.3 Beta:: 2009-04-10
   * Added a check to disable the up and down keys until the correct selections have been made.
   * Added more checks to make sure that the line and curve being selected are acceptable selections.  Pops up messageboxes letting you know if your selection was invalid.
   * Now it leaves a component where the original group/component was located.  It remains mostly untouched, but it might get a name change.
0.5 Beta:: 2009-04-11
   * Added a toolbar and icon
   * Added new mouse pointers that sort of hint at what you should be trying to click on next
   * Fixed the bug where the bent shape would not appear smoothed until you double clicked on it.  Should look good now immediately after bending.
   * Added extensions support so this plugin can be turned off through the extensions manager.
   * Added Progressbar.rb support.  It is now required.  Get it at smustard.com
0.51 Beta:: 2009-04-11
   * Made the icon transparent
   * Fixed a Mac toolbar visibility state remembering problem
 0.55 Beta:: 2010-04-21
   * Minor touchups on script syntax.
   * Tweaked a few things to make the script a little faster
=end
require 'sketchup.rb'

class Clf_shape_bender

# This is run when the tool is first instantiated.  Sets all of the instance variables, determines if the user has selected a group/component
# when the script is activated.  Starts the "undo stack"
def activate
	model = Sketchup.active_model
	sel = model.selection
	model.start_operation "Shape Bender"
	if sel.length != 1
		UI.messagebox "Please have a single group or Component to bend selected before activating the script."
		Sketchup.send_action "selectSelectionTool:"
		return
	end
	if (sel[0].class == Sketchup::Group) or (sel[0].class == Sketchup::ComponentInstance)
	else
		UI.messagebox "Please have a single group or Component to bend selected before activating the script."
		Sketchup.send_action "selectSelectionTool:"
		return
	end
	
	############# - Instance Variable Library - ################
	@line = "" 					# The line entity that is selected
	@curve = "" 				# The curve entity that is selected.  Does not have to be a curve at all. Can be a series of connected lines.
	@ip = ""
	@selection_count = 0 		# This flag helps keep track of where in the lin/curve selection process the user is at.
	@group = sel[0] 			# This is the group/component that will be transformed.  We'll make it unique if necessary.
	@comp_def = nil				# This holds the definition of the original, unexploded component.
	@orig_transformation = nil	# This holds the transformation  of the original, unexploded component.
	@container = ""				# This holds the container component that the shape is exploded into.
	@best = ""					# This holds the "best picked" entity from the pickhelper selection tool.
	@drawbest = ""				# Pickhelper element used in the onMouseMove method.
	@line_labels = [] 			# This holds the start and end labels placed on the line
	@arc_labels = []			# This holds the start and end labels placed on the arc
	@curve_lengths = []			# will store the curve lengths after @curve has been re-ordered.
	@vert_array = [] 			# correctly ordered vertex array - from the CURVE!!!!!
	@geom_verts = []			# The vertices from the geometry after its been sliced and diced.  Same order as @geom_poss
	@geom_poss = []				# The vertices.position from the geometry after its been sliced and diced.  Same order as @geom_verts
	@geom_edge_poss = []		# This is used in the preview.  Holds the positions of each edge.
	@geom_faces = []			# Holds an array of the faces in the geometry to bend.
	@geom_edges = []			# Holds an array of the edges in the geometry to bend.
	@geom_edge_start_pos = []	# Holds and array of the positions of the starting vertex for each edge in the geometry to bend.  Used in the new shape preview.
	@geom_edge_end_pos = []		# Holds and array of the positions of the ending vertex for each edge in the geometry to bend.  Used in the new shape preview.
	@line_positions = []		# 3d positions that correspond to the curve positions in length.
	@p_fac = 0					# Percentage factor of scale change from the curve to the line.  
	@line_reverse = false 		# reverses the orientation of the line if true
	@arc_endverts = [] 			# This array holds the arc start/end vertices.
	@line_endverts = [] 		# This array holds the line start/end vertices.
	@divide_ready = false 		# Gets set to true when everything is ready to begin dividing the line into segments
	@arc_seg_lengths = [] 		# Contains the lengths of all arc segments in the correct order.  Populated in get_arc_lengths
	@line_lengths = []			# Holds the lengths of the virtual line segments -  the line never actually gets divided.
	@arc_total_length = 0.0 	# Holds the total length of the arc
	@line_total_length = 0.0 	# Hold the total length of the line
	@cpoints = []				# Holds temporary construction points during testing
	@start_norms = []			# Holds the normal for the starting point of each edge of the curve
	@end_norms = []				# Holds the normal for the ending point of each edge of the curve
	@arc_seg_vectors = []		# Holds the directional vector for each edge segment of the curve, in order.
	@transform = false			# Turns true when the user hits Return, indicating that they are ready to initiate the transform.
	@trans_vecs = []			# Holds an arraoy of vectors to be applied to vertices.
	@preview_poss = []			# Holds pairs of points for all the edges that need to be drawn in the preview.
	@geom_ymax = nil			# Holds the maximum y position of the geometry to bend
	@geom_ymin = nil			# Holds the minimum y position of the geometry to bend
	@reverse = false			# Is true or false if the user has toggled the start/end points of the arc with the up arrow key.
	@x_scale_factor = 1			# This holds the amount that the shape will be scaled in the x axis
	@status_text = "Please select a single line that lies on the red axis." 	# The display text when the script starts.
	#############################################################
	Sketchup.status_text = @status_text
	if @group.class == Sketchup::Group
		@group = @group.to_component 
	end
	@comp_def = @group.definition
	@orig_transformation = @group.transformation
	@group2 = Sketchup.active_model.entities.add_instance @comp_def, @orig_transformation
	@group = @group.make_unique
	make_cursors
	onSetCursor
end # activate

# Cleans up if the tool gets de-activated during the process.  It deletes the start and end labels if they exist.
# The abort_operation returns everything back to its state right before the start_operation in the activation method.
def deactivate(view)
	(Sketchup.active_model.entities.erase_entities( @line_labels )) if (@line_labels)
	Sketchup.active_model.entities.erase_entities( @arc_labels ) if (@arc_labels)
	Sketchup.active_model.abort_operation
	view.invalidate
end # deactivate
 
# This gets run when the user suspends the tool to orbit, pan, etc.
def suspend(view)
	view.invalidate
end

# This is run when the tool is resumed after it was suspended to orbit, pan, etc.  It needs to re-set the statues text.
def resume(view)
	Sketchup.status_text = @status_text
	onSetCursor
	view.invalidate
end

# This sets all the info for the mouse pointers
def make_cursors
	line_cursor_path = Sketchup.find_support_file("line_pointer.png", "Plugins/clf_shape_bender")
	curve_cursor_path = Sketchup.find_support_file("curve_pointer.png", "Plugins/clf_shape_bender")
	default_cursor_path = Sketchup.find_support_file("default_pointer.png", "Plugins/clf_shape_bender")
	@line_pointer = UI.create_cursor(line_cursor_path, 15, 2)
	@curve_pointer = UI.create_cursor(curve_cursor_path, 15, 2)
	@default_pointer = UI.create_cursor(default_cursor_path, 15, 2)
end # make_cursors

# This determines what cursor to be displaying when.
 def onSetCursor#(view)
	UI.set_cursor(@line_pointer) if @selection_count == 0
	UI.set_cursor(@curve_pointer) if @selection_count == 1
	UI.set_cursor(@default_pointer) if @selection_count == 2
	Sketchup.active_model.active_view.invalidate
 end

# This is used to dynamically highlight/select the line or curve when you move the mouse over it.
def onMouseMove(flags, x, y, view)
	onSetCursor
	verts = []
	if @selection_count < 2
		ph = view.pick_helper
		num = ph.do_pick(x,y)
		@best = ph.best_picked
		if (@best)
			if @best.class == Sketchup::Edge
				all_best = @best.all_connected
				if @selection_count == 0
					if all_best.length == 1
						y1 = @best.start.position.y
						y2 = @best.end.position.y
						z1 = @best.start.position.z
						z2 = @best.end.position.z
						if (y1 == y2) && (z1 == z2)
							Sketchup.active_model.selection.clear
							Sketchup.active_model.selection.add( @best )
						else
							Sketchup.active_model.selection.clear
						end
					else
						Sketchup.active_model.selection.clear
					end
				end
				if @selection_count == 1
				#onSetCursor
					if all_best.length > 1
						face = false
						all_best.each { |e| (face = true) if (e.class == Sketchup::Face) }
						if !face
							all_best.each { |e| verts << e.start << e.end }
							verts.uniq!
							endpt = false
							verts.each do |e|
								endpt =  true if e.edges.length == 1
								if endpt
									Sketchup.active_model.selection.clear
									Sketchup.active_model.selection.add( all_best )
								end
							end
						end
					end
				end
			else
				Sketchup.active_model.selection.clear
			end
		else
			Sketchup.active_model.selection.clear
		end
		view.invalidate
	end
end
 
# This method runs everytime the left mouse button.  It is set up to only do something if the user is in the proces of selecting the line or the arc.
# It is this method that essentially coordinates most of the script.  It starts most of the methods.
def onLButtonUp(flags, x, y, view)
	onSetCursor
	if @selection_count < 2
		ph = view.pick_helper
		num = ph.do_pick(x,y)
		@best = ph.best_picked
		if @selection_count == 0
			line_picker
		else
			if @selection_count == 1
				curve_picker
				curve_labeler
				get_arc_lengths
				divide_line
				get_normals
				exploder
				add_vertices
				get_geom_ents
				transform_it
			end
		end
	end
end # onLButtonUp

# This method is used to determine if the user has pressed any of the scipt's keys - Up arrow/Home, or Down Arrow/end key.
# If one of these keys is pressed, this method will call the methods necessary again to re-roder the curve/line.  It also fires the transform method.
# Transform is used for the preview linework and the actual transformation.  It is the onReturn method that calls the transform method and tells it to actually
# do the bending.
def onKeyUp (key, repeat, flags, view)
	if @selection_count == 2
		if (key == VK_UP) || (key == VK_HOME)
			@preview_poss = []
			curve_ordering
			get_arc_lengths
			get_normals
			#transform_it
			curve_labeler
			transform_it
		end
	end
	if @selection_count > 0
		if (key == VK_DOWN) || (key == VK_END)
			if (@line)
				@preview_poss = []
				@line_endverts.reverse!
				line_labeler
				divide_line
				transform_it if @selection_count == 2
			end
		end
	end
end #onKeyUp


# onReturn is run every time the user hits enter.  It is set to only proceed once the @selection_count == 2, 
# meaning that the line and arc have both been chosen correctly already.
def onReturn(view)
	if @selection_count == 2
		@transform = true
		transform_it
		componentizer
		Sketchup.active_model.entities.erase_entities( @line_labels ) if (@line_labels)
		Sketchup.active_model.entities.erase_entities( @arc_labels ) if (@arc_labels)
		@line_labels = []
		@arc_labels = []
		@end_norms = []
		@line_positions = []
		Sketchup.active_model.commit_operation
		Sketchup.status_text = @status_text
		Sketchup.send_action "selectSelectionTool:"
	end
end # onReturn


def line_picker
	all_best = @best.all_connected
	if @selection_count == 0
		if all_best.length == 1
			if all_best[0].class == Sketchup::Edge
				py1 = @best.start.position.y
				py2 = @best.end.position.y
				pz1 = @best.start.position.z
				pz2 = @best.end.position.z
				if (py1 == py2) && (pz1 == pz2)
					@selection_count = 1
					@line = @best
					@line_endverts = @line.vertices
					@line_total_length = (@line.start.position.distance @line.end.position).to_f
					@status_text = "Please select an curve to bend the shape to."
					Sketchup.status_text = @status_text
					line_labeler
				else
					UI.messagebox "Please select a line that is drawn on the red axis."
				end
			else
				UI.messagebox "Please select a single straight line."
			end
		else
			UI.messagebox "Please select a single straight line first."
		end
	end
end # line_picker

# Check to see if text labels have been placed.  Deletes them if they have been created.
# Then places new text labels.
def line_labeler
	if !@line_labels.empty?
		Sketchup.active_model.entities.erase_entities @line_labels
		@line_labels = []
	end
	start_label = Sketchup.active_model.entities.add_text "Start", @line_endverts[0]
	end_label = Sketchup.active_model.entities.add_text "End", @line_endverts[1]
	@line_labels << start_label << end_label
end # line_labeler

# Check to see if text labels have been placed.  Deletes them if they have been created.
# Then places new text labels.
def curve_labeler
	if !@arc_labels.empty?
		Sketchup.active_model.entities.erase_entities @arc_labels
		@arc_labels = []
	end
	start_label = Sketchup.active_model.entities.add_text "Start", @arc_endverts[0]
	end_label = Sketchup.active_model.entities.add_text "End", @arc_endverts[1]
	num = 1
	@arc_labels << start_label << end_label
end # curve_labeler

def curve_picker
	face = false
	verts = []
	endpt = false
	all_best = @best.all_connected
	all_best.each do |e|
		if e.class == Sketchup::Face
			face = true
		end
	end
	if face
		UI.messagebox "Pleae select a series of lines with no connected faces - like an arc."
		@selection_count = 1
	else
		all_best.each { |e| verts << e.start << e.end }
		verts.uniq!
		verts.each do |e|
			endpt = true if e.edges.length == 1
		end
		if endpt
			@selection_count = 2
			@curve = all_best
			@status_text = "Enter = Accept;   UP Arrow/Home = Toggle Curve;   DOWN Arrow/End = Toggle Line;"
			Sketchup.status_text = @status_text
			curve_ordering
		else
			UI.messagebox "You can not select a close loop - like a circle."
			@selection_count = 1
		end
	end
end # curve_picker


# First takes the @curve array and reorganizes it so that it holds the edges in order from one end to the other.  
# Then it makes an array that holds all the lengths in the correct order.
def curve_ordering
	edge = []
	allverts = []
	temp_edge = [] 
	arc_first = true
	@vert_array = []
	start_vert = []
	@curve.each do |e|
		allverts << e.start << e.end
	end
	allverts.uniq!
	if @arc_endverts.empty?
		allverts.each do |e| 
			if e.edges.length == 1
				@arc_endverts << e
			end
		end
	else
		@arc_endverts.reverse!
	end
	start_vert = [@arc_endverts[0]]
	@curve.length.times do
		@vert_array << start_vert[0]
		if arc_first
			edge = start_vert[0].edges
		else
			two_edges = start_vert[0].edges
			edge = two_edges - temp_edge
		end
		temp_edge = edge
		two_verts = edge[0].vertices
		start_vert = two_verts - start_vert
		arc_first = false
	end
	@vert_array << @arc_endverts[1]
	@divide_ready = true
	curve_labeler
end # curve_ordering

# Gets the arc segment lengths and populates the @arc_seg_lengths array.
def get_arc_lengths
	distance = nil
	vert1 = nil
	vert2 = nil
	vec = []
	@arc_seg_vectors = []
	@arc_seg_lengths = []
	@x_scale_factor = []
	@arc_total_length = 0
	@vert_array.each_index do |e|
		vert1 = @vert_array[e]
		vert2 = @vert_array[e+1]
		if (vert2)
			distance = vert1.position.distance vert2.position
			vec = vert1.position.vector_to vert2.position
			@arc_seg_vectors << vec
			@arc_seg_lengths << distance
			distance = []
		end
	end
	@arc_seg_lengths.each do |e|
		@arc_total_length += e
	end
	@x_scale_factor = @arc_total_length / @line_total_length
	
end # get_arc_lengths


def divide_line
	@line_lengths = []
	@line_positions = []
	total_segs = @curve.length
	@p_fac = (@line_total_length.to_f / @arc_total_length.to_f) #This gets the percentage multiplier for each curve segment length to get scaled for the line
	@arc_seg_lengths.each do |e|
		@line_lengths << e * @p_fac
	end
	@line_vector = @line_endverts[0].position.vector_to(@line_endverts[1].position)
	lv_temp = @line_vector
	pos1 = @line_endverts[0].position
	@line_positions << pos1
	num = 1
	@line_lengths.length.times do |e|
		lv_temp.length = @line_lengths[e]
		pos2 = pos1.offset lv_temp
		@line_positions << pos2
		pos1 = pos2
		num +=1
	end
	Sketchup.active_model.active_view.invalidate
end # divide_line



def get_normals
	first_edge = true
	last_edge = false
	edge_dir = []
	edge_perp = []
	zvec = Geom::Vector3d.new(0,0,1)
	edge_dir = []
	@end_norms = []
	@end_norms << (@arc_seg_vectors[0] * zvec).reverse!
	@arc_seg_vectors.each_index do |e|
		xvec1 = (@arc_seg_vectors[e] * zvec)
		xvec2 = (@arc_seg_vectors[e+1] * zvec) if (@arc_seg_vectors[e+1])
		if( xvec2 ) # there won't be an xvec2 for the last edge, so we have to test if it exists.
			yvec2 = Geom::Vector3d.linear_combination(0.5, xvec1, 0.5, xvec2)
			@end_norms << yvec2.reverse!
		end
	end
	@end_norms << (@arc_seg_vectors.last* zvec).reverse!
	pi = 0
	@end_norms.each do |e|
		point1 = @vert_array[pi].position
		vector = e
		point2 = point1.offset vector
		pi += 1
	end
	Sketchup.active_model.active_view.invalidate
end

def exploder
	group_found = true
	@container = Sketchup.active_model.entities.add_group( @group )
	@cont_ents = @container.entities
	@group = @cont_ents[0]
	@gents = @group.definition.entities
	explodeable = []
	
	while group_found
		pb = ProgressBar.new(@gents.length,"(1 of 5) Searching for objects to explode.")
		@gents = @group.definition.entities
		group_found = false
		i = 0
		@gents.each do |e|
			if (e.class == Sketchup::ComponentInstance) or (e.class == Sketchup::Group)
				explodeable << e
				group_found = true
			end
			if e.class == Sketchup::Edge
				e.explode_curve if e.curve
			end
			i +=1
			pb.update(i)
		end
		i=0
		if !explodeable.empty?
			pb = ProgressBar.new(explodeable.length,"(2 of 5) Exploding #{explodeable.length} objects.")
			explodeable.each do |e|
				e.explode
				i+=1
				pb.update(i)
			end
			explodeable = []
		end
	end
	Sketchup.status_text = "Processing line and curve segements"
	temp_def = @group.definition
	@group.explode
	temp_def.entities.erase_entities(temp_def.entities.to_a)
end


def add_vertices
	entities = Sketchup.active_model.entities
	g_ents = @container.entities
	lines = []
	plane1 = []
	edgep1 = []
	edgep2 = []
	edge_vector = []
	recurse = false
	hidden = true
	posx1 = 0
	posx2 = 0
	ymin = @container.bounds.min.y - 10
	ymax = @container.bounds.max.y + 10
	zmin = @container.bounds.min.z - 10
	zmax = @container.bounds.max.z + 10
	pb = ProgressBar.new(@line_positions.length,"(3 of 5) Cutting the shape into #{@line_positions.length} segments.")
	i=0
	pb.update(i)
	@line_positions.each do |e|
		g_ents_before = @container.entities.to_a
		p1 = Geom::Point3d.new( e.x, ymin, zmin )
		p2 = Geom::Point3d.new( e.x, ymin, zmax )
		p3 = Geom::Point3d.new( e.x, ymax, zmax )
		p4 = Geom::Point3d.new( e.x, ymax, zmin )
		t_group = entities.add_group
		t_ents = t_group.entities
		t_ents.add_face(p1,p2,p3, p4)
		g_ents.intersect_with recurse, @container.transformation, @container.entities, @container.transformation, hidden, t_group
		entities.erase_entities t_group
		lines = g_ents.to_a - g_ents_before
		lines.each do |x|
			if x.class == Sketchup::Edge
				posx1 = x.start.position.x
				posx2 = x.end.position.x
				if posx1 == posx2
					x.smooth=true
					x.soft=true
				end
			end
		end
		g_ents = @container.entities
		g_ents_before = []
		i+=1
		pb.update(i)
	end
	Sketchup.status_text = @status_text
end



def get_geom_ents
	get_max_y
	@cont_ents = @container.entities
	pb = ProgressBar.new(@cont_ents.length,"(4 of 5) Sorting #{@cont_ents.length} pieces of geometry.")
	i=0
	pb.update(i)
	@cont_ents.each do |e|
		if e.class == Sketchup::Edge
			@geom_edges << e
			@geom_edge_poss << e.start.position.clone << e.end.position.clone
			@geom_edge_start_pos << e.start.position.clone
			@geom_edge_end_pos << e.end.position.clone
			@geom_verts << e.start << e.end
		end
		i+=1
		pb.update(i)
	end
	@geom_verts.flatten!
	@geom_verts.uniq!
	@geom_verts.each { |e| @geom_poss << e.position.clone }	
	@cont_ents = @container.entities
end # get_geom_ents


def get_max_y
	@line_y = @line_positions[0].clone.y
	@ymax = @container.bounds.max.y# * 1.2
	@ymin = @container.bounds.min.y# * 1.2
	@ypos = @ymax - @line_y
	@yneg = @line_y - @ymin
	if @ymin < @line_y
		if @ymax < @line_y
			@y_total = @line_y - @ymin
		else
			@y_total = @ymax - @ymin
		end
	else
		@y_total = @ymax - @line_y
	end
	if @ymax < @line_y
		@pos_length = 0
	else
		@pos_length = @ymax - @line_y
	end
	if @ymin >= @line_y
		@neg_length = 0
	else
		@neg_length = @line_y - @ymin
	end
end #get_max_y


def draw( view )
	pos_grid_color = [153,153,255]
	pos_border_color = [43,43,255]
	neg_grid_color = [200,0,0]
	neg_border_color = [200,0,0]
	line_arc_color = [0,200,0]
	preview_color = [0,200,0]
	select_line_color = [255,130,150]
	grid_line_width = 1
	border_line_width = 2
	preview_line_width = 1
	horizontal_count = 7
	hclast = horizontal_count
	green_count = 0
	if @selection_count == 2
		@offset = @y_total / 6
		view.drawing_color = pos_grid_color
		cc = 1
		clast = @line_positions.length
		@line_positions.each do |e|
			if (cc == 1) || (cc == clast)
				view.line_width = border_line_width
				view.drawing_color = pos_border_color 
			end
			p1 = e.clone
			p2 = p1.clone
			p3 = e.clone
			p2[1] += @pos_length
			p3[1] -= @neg_length
			(view = view.draw GL_LINES, p1, p2 ) if @pos_length != 0
			view.drawing_color = neg_grid_color
			if (cc == 1) || (cc == clast)
				view.drawing_color = neg_border_color 
			end
			(view = view.draw GL_LINES, p1, p3) if @neg_length != 0
			cc += 1
			view.line_width = grid_line_width
			view.drawing_color = pos_grid_color
		end
		np1 = @line_positions[0].clone
		np2 = @line_positions.last.clone
		if @neg_length != 0
			np1[1] -= @neg_length
			np2[1] -= @neg_length
		else
			np1[1] = @line_y
			np2[1] = @line_y
		end
		cc = 1
		horizontal_count.times do
			view.line_width = grid_line_width
			view.drawing_color = pos_grid_color
			if( cc == 1 ) || (cc == hclast)
				view.line_width = border_line_width
				view.drawing_color = pos_border_color
				if( cc == 1 )
					if np1.y < @line_y
						view.drawing_color = neg_border_color 
						green_count +=1
					end
					view = view.draw2d GL_LINES, view.screen_coords(np1), view.screen_coords(np2)
				else
					view = view.draw GL_LINES, np1, np2
				end
			else
				if np1.y < @line_y
					view.drawing_color = neg_grid_color
					green_count +=1
				end
				view = view.draw GL_LINES, np1, np2
			end
			np1[1] += @offset
			np2[1] += @offset
			cc += 1
		end
	end
	# Inside this if controls the arc grid elements - they get drawn only after
	# the @line_positions array has been populated
	if !@end_norms.empty?
		cc = 1
		pi = 0
		npi = 0
		clast = @vert_array.length
		@end_norms.each do |e|
			p1 = []
			p2 = []
			p3 = []
			hc = 1
			p1 = @vert_array[pi].position.clone
			vector = e.clone
			if @pos_length != 0
				if (cc == 1) || (cc == clast)
					view.line_width = border_line_width
					view.drawing_color = pos_border_color 
				else
					view.line_width = grid_line_width
					view.drawing_color = pos_border_color 
				end
				vector.length = @pos_length
				p2 = p1.offset vector
				view = view.draw GL_LINES, p1, p2
			end
			if @neg_length != 0
				if (cc == 1) || (cc == clast)
					view.line_width = border_line_width
					view.drawing_color = neg_border_color
				else
					view.line_width = grid_line_width
					view.drawing_color = neg_grid_color
				end
				vector.length = -@neg_length
				p3 = p1.offset vector
				view.drawing_color = neg_border_color
				view = view.draw GL_LINES, p1, p3
			end
			if cc < @vert_array.length
				view.line_width = grid_line_width
				view.drawing_color = pos_grid_color
				vector1 = @end_norms[pi].clone
				vector2 = @end_norms[pi+1].clone					
				if @neg_length != 0
					np1 = @vert_array[pi].position.clone
					np2 = @vert_array[pi+1].position.clone
					vector1.length = -@neg_length
					vector2.length = -@neg_length
					np1 = np1.offset vector1
					np2 = np2.offset vector2
					vector1.length = -@offset
					vector2.length = -@offset
				else
					np1 = @vert_array[pi].position.clone
					np2 = @vert_array[pi+1].position.clone
					vector1.length = @offset
					vector2.length = @offset
				end
				hc = 1
				horizontal_count.times do
					if( hc == 1 ) || ( hc == hclast )
						view.line_width = border_line_width
						if hc <= green_count
							view.drawing_color = neg_border_color
						else
							view.drawing_color = pos_border_color
						end
						view = view.draw GL_LINES, np1, np2
					else
						if hc <= green_count
							view.drawing_color = neg_grid_color
						else
							view.drawing_color = pos_grid_color
						end
						view.line_width = grid_line_width
						view = view.draw GL_LINES, np1, np2
					end
					np1 = np1.offset vector1
					np2 = np2.offset vector2
					hc += 1
					view.drawing_color = pos_grid_color
				end
			end
			cc += 1
			pi += 1
			npi += 1
		end
	end
	# Displays the preview geometry.
	if !@preview_poss.empty?
		view.line_width = preview_line_width
		view.drawing_color = preview_color
		loop_times = (@preview_poss.length / 2 )
		start_index = 0
		end_index = 1
		loop_times.times do
			p1 = @preview_poss[start_index]
			p2 = @preview_poss[end_index]
			view = view.draw2d GL_LINES, view.screen_coords(p1), view.screen_coords(p2)
			start_index += 2
			end_index += 2
		end
	end
end # draw


def transform_it
	orig_pos = []
	new_pos = []
	point = []
	line_x = 0
	orig_x_from_line = 0
	temp_line_x_pos = []
	vector_x_fit = []
	temp_offset_x = []
	temp_line_x = []
	line_x_pos = []
	edge_found = false
	corresponding_edge = []
	weight_point = 0
	vector = []
	vec1 = []
	vec2 = []
	re_x = @container.transformation.origin.clone.x
	re_y = @container.transformation.origin.clone.y
	line_transform_position = []
	line_vec = Geom::Vector3d.new 1,0,0
	temp_g = Sketchup.active_model.entities.add_group
	gents = temp_g.entities
	# Ths point of this is just to create an array of vectors that defin where a point should originate and move to.  To do this, it creates an original_position and a new_position.  The original position must be 
	# set in relation its corresponding edge startpointposition.  And the new_position comes from transforming that point by offset, using the curve's corresponding edge start point position, that edge's vector
	# as the x axis, a linear_combination of its end norms as the y axis and 0,0,1 as the z axis.
	temp_lines = Sketchup.active_model.entities.add_group
	t_ents = temp_lines.entities
	positions = @geom_poss if @transform
	positions = @geom_edge_poss if !@transform
	if @transform
		pb = ProgressBar.new(positions.length,"(1 of 1) Bending #{positions.length} vertices.")
	else
		pb = ProgressBar.new(positions.length,"(1 of 1) Locating #{positions.length} test points.")
	end
	i=0
	pb.update(i)
	positions.each do |e|
		line_vec = Geom::Vector3d.new 1,0,0
		orig_pos = e.clone
		orig_pos[0] = e.x + re_x
		orig_pos[1] = e.y + re_y
		orig_moved = orig_pos.clone
		orig_untouched = e.clone
		pos_x = orig_pos.x
		corresponding_edge = 0
		edge_found = false
		last_x = false
		pre_curve = false
		if pos_x < @line_positions[0].x
			edge_found = true
			pre_curve = true
		else
			line_x_pos = @line_positions[1].clone
			line_x = line_x_pos.x
		end
		until edge_found
			if pos_x < line_x
				edge_found = true
			else
				corresponding_edge += 1
				if corresponding_edge == (@line_positions.length) - 1
					corresponding_edge -= 1
					line_x_pos = @line_positions[corresponding_edge].clone
					edge_found = true
					last_x = true
				else
					line_x_pos = @line_positions[corresponding_edge+1].clone
				end
				line_x = line_x_pos.x
			end
		end
		line_transform_position = @line_positions[corresponding_edge].clone
		orig_pos[0] = orig_moved.x - line_transform_position.x
		orig_pos[1] = orig_moved.y - line_transform_position.y
		temp_line_x_pos = @line_positions[corresponding_edge].clone
		temp_line_x = temp_line_x_pos.x
		orig_x_from_line = pos_x - temp_line_x
		new_x_distance = orig_x_from_line * @x_scale_factor
		if new_x_distance != 0
			line_vec.length = new_x_distance
			temp_line_x_pos.offset! line_vec
			orig_pos[0] = temp_line_x_pos.x - temp_line_x
		end
		vec1 = @end_norms[corresponding_edge]
		vec2 = @end_norms[corresponding_edge+1]	
		weight_point = line_x.to_f - pos_x.to_f
		first_weight = weight_point/@line_lengths[corresponding_edge]
		second_weight = 1.0 - first_weight
		yvec = Geom::Vector3d.linear_combination( first_weight, vec1, second_weight, vec2 )
		if last_x
			yvec = @end_norms.last
		end
		if pre_curve
			yvec = @end_norms.first
		end
		x_ax = @arc_seg_vectors[corresponding_edge]
		tp = @vert_array[corresponding_edge].position.clone
		yvec.normalize!
		t = Geom::Transformation.axes tp, x_ax, yvec, [0,0,1]
		new_pos = orig_pos.transform t
		vector = orig_moved.vector_to new_pos
		if !@transform 
			point = e.offset vector
			point[0] = point[0] + re_x
			point[1] = point[1] + re_y
			@preview_poss << point
		else
			@trans_vecs << vector
		end
		i+=1
		pb.update(i)
	end
	Sketchup.status_text = "Finalizing all bending." if @transform
	@cont_ents.transform_by_vectors( @geom_verts, @trans_vecs ) if @transform
end #transform_it

# This method is run after the shape is transformed.  It makes sure that it is a component and gives it the "Shape Bender Component" name.
# First it moves @container into a temp group, and then explodes @container.  This is because there is a bug where SU won't apply the softe edges until
# @container has been double clicked on, or exploded.  I can't double click in and out in ruby, but I can explode it!  So I put it into a temp group first.
def componentizer
	temp_group = Sketchup.active_model.entities.add_group( @container )
	explode_container = temp_group.entities[0]
	explode_container.explode
	tc = temp_group.to_component
	tcn = tc.definition
	tcn.name = "Shape Bender Component"
end # componentizer

end # class Clf_shape_bender


#-------------  MENU SYSTEM ---------------------------#
if( $submenu )
	organizer = $submenu 
	if !file_loaded?(__FILE__) then
		toolbar = UI::Toolbar.new "Shape Bender"
		cmd1 = UI::Command.new("Shape Bender") { Sketchup.active_model.select_tool Clf_shape_bender.new }
		organizer.add_item cmd1
		cmd1.small_icon = "clf_shape_bender_small.png"
		cmd1.large_icon = "clf_shape_bender.png"
		cmd1.tooltip = "Shape Bender - Select a shape to bend first"
		toolbar = toolbar.add_item cmd1
		case toolbar.get_last_state
		when 1
			toolbar.restore
		when -1   
			toolbar.show
		end
	end
else
	if !file_loaded?('clf_menu_loader')
		$clf_tools_menu = UI.menu("Plugins").add_submenu("Chris Fullmer Tools")
	end
	if !file_loaded?('clf_youtube_loader')
		$clf_youtube_menu = $clf_tools_menu.add_submenu("-=YouTube Video Tutorials For My Scripts=-")
		$clf_youtube_menu.add_item("My YouTube Channel"){UI.openURL "http://www.youtube.com/user/ChrisFullmer"}
	end
	if !file_loaded?('clf_myscipts_loader')
		$clf_tools_menu.add_item("-=All My Plugins I've Written=-"){UI.openURL "http://forums.sketchucation.com/search.php?keywords=plugin&terms=all&author=Chris+Fullmer&sc=1&sf=titleonly&sr=topics&sk=t&sd=d&st=0&ch=300&t=0&submit=Search"}
	end
#	------YouTube for this Script------------------
	$clf_youtube_menu.add_item("Shape Bender"){UI.openURL "http://www.chrisfullmer.com/plugins/clf_shape_bender.html"}		
#	------Donate to the Author--------------------
	if !file_loaded?('clf_paypal_loader')
		$clf_tools_menu.add_item("-=Donate $$$ to Chris via PayPal=-"){UI.openURL "http://www.chrisfullmer.com/plugins/donate.html"}
		$clf_tools_menu.add_separator
	end
#	------------------------------------------------

#	------------------------------------------------
	if !file_loaded?(__FILE__) then
		toolbar = UI::Toolbar.new "Shape Bender"
		cmd1 = UI::Command.new("Shape Bender") { Sketchup.active_model.select_tool Clf_shape_bender.new }
		$clf_tools_menu.add_item cmd1
		cmd1.small_icon = "clf_shape_bender_small.png"
		cmd1.large_icon = "clf_shape_bender.png"
		cmd1.tooltip = "Shape Bender - Select a shape to bend first"
		toolbar = toolbar.add_item cmd1
		case toolbar.get_last_state
		when 1
			toolbar.restore
		when -1   
			toolbar.show
		end
	end
end

file_loaded('clf_myscipts_loader')
file_loaded('clf_paypal_loader')
file_loaded('clf_youtube_loader')
file_loaded('clf_menu_loader')
file_loaded(__FILE__)