# Supports Organizer.rb
=begin rdoc
= Perpendicular Face Tools
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
Portions of the code Copyright 2005-2007, Google, Inc. and can be found in utilities.rb that comes with Sketchup.
The rest is Copyright 2009, Chris Fullmer
You may not alter, publish, market, distribute, give, transfer, sell or sublicense the Scripts or any part of this Script.

== Information
Author:: Chris Fullmer
Organization:: www.ChrisFullmer.com and distributed on www.sketchucation.com
Name:: Perpendicular Face Tools
Version:: 1.1 
SU Version:: I developed this on SketchUp version 7, but older versions should work.
Date:: 03-25-09
Description:: Three Perpendicular Face tools to help create faces perpendicular to the end of lines.
Usage:: Activate it at Plugins>Chris Fullmer Tools>Perpendicular Line Tools.  Change the size of squares by entering a new dimension into the measuerments box after the face has been created.  Change the radius of circles by entering "12s" for 12 sides into the measurements box.  To use the custom face tool, create your face flat on the groundplane.  The script attempts to line up the x axis as the bottom edge of the face. 
Requires:: No additional scripts
History::
0.1 beta:: 2009-03-11
   * Original release
   * Organizer.rb support. If you place this script into an organizer directory, it will appear in the correct submenu.  If its just in the plugins folder, it will appear in "Chris Fullmer Tools" submenu of the Plugins Menu."
   * No great way to align faces to the endpoints.  Sorry, they just sort of align themselves.
1.0:: 2009-03-25
   * The original face now explodes upon exiting the custom face tool.
   * Added support so that faces will stay oriented with a flat edge on the horizontal plane.
   * Holding ctrl while creating a face will over-ride the flat horizontal feature and revert back to the default SketchUp "Arbitrary Rotation".
   * Now faces can be added anywhere to a line - on the endpoint (existing feature), in the middle of a line, or on a vertex of 2 lines.  The vertex of 3 lines is not supported.
1.1:: 2009-03-25
   * Attempted to fix some bugs with the custom face tool.  It only was working once in a while.  And only if the blue face was up.  Now it is slightly better.  But there are still times when it will need to be rotated 180degrees after it is placed.  sorry :(   
   * Added a Toolbar!
1.2:: 2009-03-25
   * Changed so that circles default to have their vertices on top and bottom, not edges.
   * The up arrow key rotates the faces after they have been placed.  Squares will rotate 45 degrees. Circles will rotate the angle needed to alternate between having an edge or a vertex on the top. Custom faces rotate in 90 degree increments.  Helpful for fixing faces that are oriented 180 off, or that were drawn on the wrong axis.
=end


require 'sketchup.rb'

# This is the tool that makes lines following the normals of the point where its made.
class Clf_perpendicular_square_face
@@ls = ""

def activate
    @ip = Sketchup::InputPoint.new
    @iptemp = Sketchup::InputPoint.new
    @displayed = false
    @sp = []
    @end_pos = []
    @ctrlflag = 0
	@group = nil
    if @@ls.to_s == ""
        @@ls = 100
    end
    Sketchup.set_status_text @@ls.to_l, SB_VCB_VALUE
    Sketchup.vcb_label = "Face Width x Height"
end

def onKeyUp(key, repeat, flags, view)
	if key == 38 && (@group)
		deg = 45
		t = Geom::Transformation.rotation @sp, @norm1, deg.degrees
		@group.transform! t
	end
end

def rotation group, point
    model = Sketchup.active_model
    entities = model.entities
    #selection = model.selection
    g = group # the face group to transform
    es = [] # edge startpoint
    ee = [] # edge endpoint
    face_points = [] # all the vertex positions of the face
    gents = g.entities # All the entities of the group
    gt = g.transformation # the transformation that has been applied to the group
    # Iterates each entity of the group.  If its an edge, it determines if the ending and start 3dpoints have been added to the face_points array.
    # If they have not been added, it adds them.  Leaving a face_array with the position of all the vertices of the face.
    gents.each do |e|
        if e.typename == "Edge"
            es = e.start.position
            ee = e.end.position
            if !(face_points.include?( es ))
                face_points << es
            end
            if !(face_points.include?( ee ))
                face_points << ee
            end
        end
    end
    p1 = face_points[1] #probably should be fixed to be more scalable.  I need a method to capture the desired 2 points - maybe?
    p2 = face_points[2]
    ex_vector = p1.vector_to p2
    ex_vector.transform! gt
    ex_vector.normalize!
    face_points.each do |e|
        e.transform! gt
    end
    flat_plane = Geom.fit_plane_to_points( [0,0,0], [5,5,0], [0,5,0] )
    group_plane = Geom.fit_plane_to_points face_points
    line = Geom.intersect_plane_plane(flat_plane, group_plane)
    new_vector = line[1]
    deg = new_vector.angle_between ex_vector
    test_vector = ex_vector.cross new_vector
	if test_vector.length != 0
		sd = test_vector.samedirection? @norm1
	end
    vector = g.transformation.yaxis
    if sd
    deg = -deg
    end
    t=Geom::Transformation.rotation point, vector, deg
    g.transform! t
end

def onMouseMove(flags, x, y, view)
    pos = @ip.position
    Sketchup::set_status_text('Click on a line-end to place a perpendicular square on it. ctrl = Use "Arbitrary Rotation"')
    @iptemp.pick view, x, y
    if( @iptemp.valid? )
        changed = @iptemp != @ip
        @ip.copy! @iptemp
        pos = @ip.position;           
        if( changed and (@ip.display? or @displayed) )
            view.invalidate
        end
    end
end

 def enableVCB?
   return true
 end

def resume view
    Sketchup.set_status_text @@ls.to_l, SB_VCB_VALUE
    Sketchup.vcb_label = "Face Width x Height"
    Sketchup::set_status_text('Click on a line-end to place a perpendicular square on it. ctrl = Use "Arbitrary Rotation"')
end

def onUserText(text, view)
        @@ls = text.to_l
        if( @group )
            @group.erase!
        end
        Sketchup.set_status_text @@ls.to_l, SB_VCB_VALUE
        make_face
end # onUserTexT

# This method makes a line between @sp and @end_pos.  
# @sp is set when the user clicks. @end_pos is determined in the endpoint_maker method
def make_face
    model = Sketchup.active_model
    entities = model.active_entities
    model.start_operation "Perpendicular Square"
    spx = @sp.x
    spy = @sp.y
    spz = @sp.z
    ls = @@ls / 2
    @pt1 = [(spx-ls), spy, (spz+ls)]
    @pt2 = [(spx-ls), spy, (spz-ls)]
    @pt3 = [(spx+ls), spy, (spz-ls)]
    @pt4 = [(spx+ls), spy, (spz+ls)]
    @group = entities.add_group
    ents = @group.entities
    face = ents.add_face(@pt1, @pt2, @pt3, @pt4)
    norm2 = face.normal
    v = @norm1.cross norm2
    angle = @norm1.angle_between norm2
    t = Geom::Transformation.rotation @sp, v.reverse, angle
    @group.transform! t
    if !@ctrlflag
        rotation @group, @sp
    end
    model.commit_operation
end #make_face

# onLButtonUp is called when the user releases the left mouse button
def onLButtonUp(flags, x, y, view)
    @sp = @ip.position
    vertex = @ip.vertex
    face = @ip.face
    edge = @ip.edge
    @norm1 = nil
	vec1 = nil
	vec2 = nil
    if flags == 8
        @ctrlflag = true
    else
        @ctrlflag = false
    end
    if(vertex)
        edges = vertex.edges
        if edges.length == 2 
			vec1 = edges[0].line[1]
			vec2 = edges[1].line[1]
			@norm1 = Geom::Vector3d.linear_combination( 0.5, vec1, 0.5, vec2 )
			edge = nil
        end
		if edges.length == 1
            edge = edges[0]
            @norm1 = edge.line[1]
			edge = nil
        end
		if edges.length > 2 
			UI.messagebox "You must select the end of a single edge"
			return
		end
    end
    if(edge)
		@norm1 = edge.line[1]
	end
    if(@norm1)
        make_face
    end
end

def draw(view)
    if( @ip.valid? && @ip.display? )
        @ip.draw view
        @displayed = true
    else
        @displayed = false
    end
end
end
















# This is the tool that makes lines following the normals of the point where its made.
class Clf_perpendicular_circle_face
@@ls = ""
@@numsegs = 24
def activate
    @ip = Sketchup::InputPoint.new
    @iptemp = Sketchup::InputPoint.new
    @displayed = false
    @sp = []
    @end_pos = []
	@ctrl_flag = 0
	@group = nil
    if @@ls.to_s == ""
        @@ls = 100
    end
    Sketchup.set_status_text @@ls.to_l, SB_VCB_VALUE
    Sketchup.vcb_label = "Face Width x Height"
end

def onMouseMove(flags, x, y, view)
    pos = @ip.position
    Sketchup::set_status_text('Click on a line-end to place a perpendicular circle on it. ctrl = Use "Arbitrary Rotation"')    
    @iptemp.pick view, x, y
    if( @iptemp.valid? )
        changed = @iptemp != @ip
        @ip.copy! @iptemp
        pos = @ip.position           
        if( changed and (@ip.display? or @displayed) )
            view.invalidate
        end
    end
end

 def enableVCB?
   return true
 end

def resume view
    Sketchup.set_status_text @@ls.to_s, SB_VCB_VALUE
    Sketchup.vcb_label = "Circle Radius"
    Sketchup::set_status_text('Click on a line-end to place a perpendicular circle on it. ctrl = Use "Arbitrary Rotation"')
end

def onUserText(text, view)
    to_split = text
    test_numsegs = to_split.split(//)
    if test_numsegs[(test_numsegs.length - 1)].to_s == "s"
        @@numsegs = text.chomp.to_i
        Sketchup.set_status_text @@ls, SB_VCB_VALUE
    else
        @@ls = text.to_l
        Sketchup.set_status_text @@ls.to_l, SB_VCB_VALUE
    end
    if @group
        @group.erase!
    end
    make_face
end # onUserText













def rotation group, point
    model = Sketchup.active_model
    entities = model.entities
    g = group # the face group to transform
    es = [] # edge startpoint
    ee = [] # edge endpoint
    face_points = [] # all the vertex positions of the face
    gents = g.entities # All the entities of the group
    gt = g.transformation # the transformation that has been applied to the group
    # Iterates each entity of the group.  If its an edge, it determines if the ending and start 3dpoints have been added to the face_points array.
    # If they have not been added, it adds them.  Leaving a face_array with the position of all the vertices of the face.
    gents.each do |e|
        if e.typename == "Edge"
            es = e.start.position
            ee = e.end.position
            if !(face_points.include?( es ))
                face_points << es
            end
            if !(face_points.include?( ee ))
                face_points << ee
            end
        end
    end
    p1 = face_points[1] #probably should be fixed to be more scalable.  I need a method to capture the desired 2 points - maybe?
    p2 = face_points[2]
    ex_vector = p1.vector_to p2
    ex_vector.transform! gt
    ex_vector.normalize!
    face_points.each do |e|
        e.transform! gt
    end
    flat_plane = Geom.fit_plane_to_points( [0,0,0], [5,5,0], [0,5,0] )
    group_plane = Geom.fit_plane_to_points face_points
    line = Geom.intersect_plane_plane(flat_plane, group_plane)
    new_vector = line[1]
    deg = new_vector.angle_between ex_vector
    test_vector = ex_vector.cross new_vector
	if test_vector.length != 0
		sd = test_vector.samedirection? @norm1
	end
    vector = g.transformation.yaxis
    if !sd
		deg = -deg
    end
    t=Geom::Transformation.rotation point, vector, deg
    g.transform! t
	#this is to make it so that the vertices are on top and bottom by default.
	deg = (((360/@@numsegs) / 2))
	t = Geom::Transformation.rotation @sp, @norm1, deg.degrees
	@group.transform! t
end














# This method makes the circle using variables that have already been set.
def make_face
    model = Sketchup.active_model
    model.start_operation "Perpendicular Circle"
    entities = model.entities
    @group = entities.add_group
    ents = @group.entities
	vec = Geom::Vector3d.new( 0,1,0 )
	origin = [0,0,0]
    edgearray = ents.add_circle @sp, vec, @@ls.to_i, @@numsegs.to_i
    face = ents.add_face edgearray.to_a
	norm2 = face.normal
    v = @norm1.cross norm2
    angle = @norm1.angle_between norm2
	t = Geom::Transformation.rotation @sp, v.reverse, angle
    @group.transform! t
	
	if !@ctrlflag
		rotation @group, @sp
    end
    model.commit_operation
end #make_face

def onKeyUp(key, repeat, flags, view)
	if key == 38 && (@group)
		deg = (((360/@@numsegs) / 2))
		t = Geom::Transformation.rotation @sp, @norm1, deg.degrees
		@group.transform! t
	end
end

# onLButtonUp is called when the user releases the left mouse button
def onLButtonUp(flags, x, y, view)
    @sp = @ip.position
    @group = nil
    vertex = @ip.vertex
    edge = @ip.edge
    @norm1 = nil
	vec1 = nil
	vec2 = nil
	if flags == 8
        @ctrlflag = true
    else
        @ctrlflag = false
    end
	
    if(vertex)
        edges = vertex.edges
        if edges.length == 2 
			vec1 = edges[0].line[1]
			vec2 = edges[1].line[1]
			@norm1 = Geom::Vector3d.linear_combination( 0.5, vec1, 0.5, vec2 )
			edge = nil
        end
		if edges.length == 1
            edge = edges[0]
            @norm1 = edge.line[1]
			edge = nil
        end
		if edges.length > 2 
			UI.messagebox "You must select the end of a single edge"
			return
		end
    end
	if(edge)
		@norm1 = edge.line[1]
	end
    if(@norm1)
        make_face
    end
end

def draw(view)
    if( @ip.valid? && @ip.display? )
        @ip.draw view
        @displayed = true
    else
        @displayed = false
    end
end
end


 #This is the tool that makes lines following the normals of the point where its made.
class Clf_perpendicular_custom_face
@@ls = ""

def activate
    @ip = Sketchup::InputPoint.new
    @iptemp = Sketchup::InputPoint.new
    @displayed = false
    @sp = []
    @end_pos = []
	@reverser = false
	@ctrlflag = 0
	@group = nil
	@group2 = nil
    face_selected = []
    if @@ls.to_s == ""
        @@ls = 100
    end
    model = Sketchup.active_model
    selection = model.selection
    selection.each do |e|
        if e.typename == "Face"
            face_selected.push e
        end
    end
    if face_selected.length != 1
        UI.messagebox "You must have a single face selected before activating this tool."
        Sketchup.send_action "selectSelectionTool:"
    end
    Sketchup::set_status_text('Click on a line-end to apply the face to. ctrl = Use "Arbitrary Rotation"')
    entities = model.active_entities
    @face = face_selected[0]
	fn = @face.normal
	if fn.z > 0
		@reverser = true
	end
    @first = true
end

def onKeyUp(key, repeat, flags, view)
	if key == 38 && (@group2)
		deg = 90
		t = Geom::Transformation.rotation @sp, @norm1, deg.degrees
		@group2.transform! t
	end
end

def resume view
    Sketchup::set_status_text('Click on a line-end to apply the face to. ctrl = Use "Arbitrary Rotation"')
end

def onMouseMove(flags, x, y, view)
    pos = @ip.position    
    @iptemp.pick view, x, y
    if( @iptemp.valid? )
        changed = @iptemp != @ip
        @ip.copy! @iptemp
        pos = @ip.position;           
        if( changed and (@ip.display? or @displayed) )
            view.invalidate
        end
    end
end

def rotation group, point
    model = Sketchup.active_model
    entities = model.entities
    g = group # the face group to transform
    es = [] # edge startpoint
    ee = [] # edge endpoint
    face_points = [] # all the vertex positions of the face
    gents = g.entities # All the entities of the group
    gt = g.transformation # the transformation that has been applied to the group
    # Iterates each entity of the group.  If its an edge, it determines if the ending and start 3dpoints have been added to the face_points array.
    # If they have not been added, it adds them.  Leaving a face_array with the position of all the vertices of the face.
    gents.each do |e|
        if e.typename == "Edge"
            es = e.start.position
            ee = e.end.position
            if !(face_points.include?( es ))
                face_points << es
            end
            if !(face_points.include?( ee ))
                face_points << ee
            end
        end
    end
	ex_vector = Geom::Vector3d.new(1, 0, 0)
	ex_vector.transform! gt
    face_points.each do |e|
		e.transform! gt
    end
    flat_plane = Geom.fit_plane_to_points( [0,0,0], [5,5,0], [0,5,0] )
    group_plane = Geom.fit_plane_to_points face_points
	if group_plane[0] < 0
		group_plane[0] = -group_plane[0]
		group_plane[1] = -group_plane[1]
		group_plane[2] = -group_plane[2]
		group_plane[3] = -group_plane[3]
	end
    line = Geom.intersect_plane_plane(flat_plane, group_plane)
    new_vector = line[1]
    deg = new_vector.angle_between ex_vector
    test_vector = ex_vector.cross new_vector
	if (test_vector.z > 0 && test_vector.x < 0)
		deg = (new_vector.angle_between ex_vector)
		deg = deg + 180.degrees
	end
	if (test_vector.z < 0 && test_vector.x > 0)
		deg = (new_vector.angle_between ex_vector)
	end
	if (test_vector.z < 0 && test_vector.x < 0)
		deg = (new_vector.angle_between ex_vector)
		deg = deg + 180.degrees
	end
	if (test_vector.z > 0 && test_vector.x > 0)
		deg = (new_vector.angle_between ex_vector)
	end
	if (test_vector.x == 0) && (test_vector.y == 0) && (test_vector.z == 0)
		deg = deg + 180.degrees
	end
	vector = gt.zaxis
    t=Geom::Transformation.rotation point, vector, deg
	g.transform! t
end

 def deactivate(view)
   @group.explode
 end

# This method makes a line between @sp and @end_pos.  
# @sp is set when the user clicks. @end_pos is determined in the endpoint_maker method
def make_face
    model = Sketchup.active_model
    model.start_operation "Perpendicular Face"
	entities = model.entities
	if @first 
		@group = entities.add_group ( @face )
	end
	@group2 = @group.copy.make_unique
	if @reverser
		t = Geom::Transformation.rotation @group.bounds.center, @group.transformation.yaxis, 180.degrees
		@group2.transform! t
	end
    sp1 = @group2.bounds.center
    ax = sp1.x
    ay = sp1.y
    az = sp1.z
    bx = @sp.x
    by = @sp.y
    bz = @sp.z
    distance = [(bx-ax), (by-ay), (bz-az)]
    vector = Geom::Vector3d.new distance
    t = Geom::Transformation.translation vector
    @group2.transform! t
    norm2 = @face.normal
    v = @norm1.cross norm2
    angle = @norm1.angle_between norm2
	if v.length == 0
		t = Geom::Transformation.rotation @sp, @group2.transformation.zaxis, angle
	else
		t = Geom::Transformation.rotation @sp, v.reverse, angle
	end
    @group2.transform! t
    @first = false
	puts "ctrlflag = " + @ctrlflag.to_s
	if !@ctrlflag
		rotation @group2, @sp
	end
    model.commit_operation
end #make_face

# onLButtonUp is called when the user releases the left mouse button
def onLButtonUp(flags, x, y, view)
    @sp = @ip.position
    vertex = @ip.vertex
    edge = @ip.edge
    @norm1 = nil
	vec1 = nil
	vec2 = nil
	if flags == 8
        @ctrlflag = true
    else
        @ctrlflag = false
    end
    if(vertex)
        edges = vertex.edges
        if edges.length == 2 
			vec1 = edges[0].line[1]
			vec2 = edges[1].line[1]
			@norm1 = Geom::Vector3d.linear_combination( 0.5, vec1, 0.5, vec2 )
			edge = nil
        end
		if edges.length == 1
            edge = edges[0]
            @norm1 = edge.line[1]
			edge = nil
        end
		if edges.length > 2 
			UI.messagebox "You must select the end of a single edge"
			return
		end
    end
	if(edge)
		@norm1 = edge.line[1]
	end
    if(@norm1)
        make_face
    end
end

    def draw(view)
        if( @ip.valid? && @ip.display? )
            @ip.draw view
            @displayed = true
        else
            @displayed = false
        end
    end
end

if( $submenu )
    organizer = $submenu 
    if !file_loaded?(__FILE__) then
        clf_perpendicular_face_tools = organizer.add_submenu("Perpendicular Face Tools")
		
        toolbar = UI::Toolbar.new "Perpendicular Face Tools"
        cmd1 = UI::Command.new("Perpendicular Square") { Sketchup.active_model.select_tool Clf_perpendicular_square_face.new }
        cmd2 = UI::Command.new("Perpendicular Circle") { Sketchup.active_model.select_tool Clf_perpendicular_circle_face.new }
        cmd3 = UI::Command.new("Perpendicular Custom Face") { Sketchup.active_model.select_tool Clf_perpendicular_custom_face.new }
        
        clf_perpendicular_face_tools.add_item cmd1
        clf_perpendicular_face_tools.add_item cmd2
        clf_perpendicular_face_tools.add_item cmd3
		
		cmd1.small_icon = "square_16.png"
		cmd1.large_icon = "square_24.png"
		cmd2.small_icon = "circle_16.png"
		cmd2.large_icon = "circle_24.png"
		cmd3.small_icon = "star_16.png"
		cmd3.large_icon = "star_24.png"
		
		cmd1.tooltip = "Perpendicular Square"
		cmd2.tooltip = "Perpendicular Circle"
		cmd3.tooltip = "Perpendicular Custom Face"
		toolbar = toolbar.add_item cmd1
		toolbar = toolbar.add_item cmd2
		toolbar = toolbar.add_item cmd3
		toolbar.show
    end
else
    if !file_loaded?('clf_menu_loader') then
        $clf_tools_menu = UI.menu("Plugins").add_submenu("Chris Fullmer Tools")
    end
    if !file_loaded?(__FILE__) then
        clf_perpendicular_face_tools = $clf_tools_menu.add_submenu("Perpendicular Face Tools")
		
		toolbar = UI::Toolbar.new "Perpendicular Face Tools"
        cmd1 = UI::Command.new("Perpendicular Square") { Sketchup.active_model.select_tool Clf_perpendicular_square_face.new }
        cmd2 = UI::Command.new("Perpendicular Circle") { Sketchup.active_model.select_tool Clf_perpendicular_circle_face.new }
        cmd3 = UI::Command.new("Perpendicular Custom Face") { Sketchup.active_model.select_tool Clf_perpendicular_custom_face.new }
    
        clf_perpendicular_face_tools.add_item cmd1
        clf_perpendicular_face_tools.add_item cmd2
        clf_perpendicular_face_tools.add_item cmd3
		
		cmd1.small_icon = "square_16.png"
		cmd1.large_icon = "square_24.png"
		cmd2.small_icon = "circle_16.png"
		cmd2.large_icon = "circle_24.png"
		cmd3.small_icon = "star_16.png"
		cmd3.large_icon = "star_24.png"
		
		cmd1.tooltip = "Perpendicular Square"
		cmd2.tooltip = "Perpendicular Circle"
		cmd3.tooltip = "Perpendicular Custom Face"
		toolbar = toolbar.add_item cmd1
		toolbar = toolbar.add_item cmd2
		toolbar = toolbar.add_item cmd3
		toolbar.show
    end
end

file_loaded('clf_menu_loader')
file_loaded(__FILE__)