# See the loader file for license and author info

require 'sketchup.rb'
module CLF_Extensions_NS
	module CLF_Shape_Bender
    class Clf_shape_bender_tool

      # 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", true)
        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/images")
        curve_cursor_path = Sketchup.find_support_file("curve_pointer.png", "Plugins/clf_shape_bender/images")
        default_cursor_path = Sketchup.find_support_file("default_pointer.png", "Plugins/clf_shape_bender/images")
        @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
  end
end
