=begin
#-------------------------------------------------------------------------------------------------------------------------------------------------
#*************************************************************************************************
# Designed by Fredo6 - Copyright November 2008

# Permission to use this software for any purpose and without fee is hereby granted
# Distribution of this software for commercial purpose is subject to:
#  - the expressed, written consent of the author
#  - the inclusion of the present copyright notice in all copies.

# THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
#-----------------------------------------------------------------------------
# Name			:   FredoScale_Box.rb
# Original Date	:   8 Mar 2009 - version 2.0
# Description	:   Implement the Generic Interactive Tool of FredoScale deformations
#-------------------------------------------------------------------------------------------------------------------------------------------------
#*************************************************************************************************
=end

module F6_FredoScale

#--------------------------------------------------------------------------------------------------------------
# Free Scale Sketchup Tool class
# Used as a generic class for all derived tools, and covers the Scale tool
#--------------------------------------------------------------------------------------------------------------			 				   

class ScaleTool

FSC_Handle = Struct.new "FSC_Handle", :ih, :oih, :pt3d, :pt2d, :lbox, :lbox2d, :lquads, :lquads2d, :type,
                                      :opp, :lpads, :lpadlines, :lpadzone, :dimside, :unusable
FSC_Hilight = Struct.new "FSC_Hilight", :ih, :oih, :iface, :ihcenter
FSC_RotateParam = Struct.new "FSC_RotateParam", :origin, :normal, :basedir, :angle

@@persistence = nil

#Class Instance initialization - Essentially for static parameters and variables
def initialize(hparam)

	#Initiating the behavior parameteres
	@hparam = hparam
	@mode_target = hparam["Mode_Target"]
	@model = Sketchup.active_model
	@selection = @model.selection
	@view = @model.active_view
	@lst_operation = []
	init_behavior
	persistence_restore
	
	#Static parameters for the FredoScale tool
	@bbox = nil
	@NUL_AXIS = Geom::Vector3d.new 0, 0, 0
	@lnumref = [0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7]
	@qnumref = [0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 4, 5, 1, 2, 5, 6, 2, 3, 6, 7, 3, 0, 7, 4]
	@lnumcenter = [[], [0, 1], [0, 2], [0, 6]]
	@xyz_handle = [[-1, -1], [1, -1], [1, 1], [-1, 1]]
	@xyz_handle = @xyz_handle.collect { |a| Geom::Point3d.new a[0], a[1], -1 } + 
				  @xyz_handle.collect { |a| Geom::Point3d.new a[0], a[1], 1 }
	@name_axes = ["Red", "Green", "Blue"]
	@ip = Sketchup::InputPoint.new	
	@tr_identity = Geom::Transformation.new
	
	@face_ref = [[0, 1, 2, 3], [4, 5, 6, 7], [0, 1, 5, 4], [1, 2, 6, 5], [2, 3, 7, 6], [3, 0, 4, 7],
	             [8, 9, 10, 11], [12, 13, 14, 15], [8, 12, 16, 17],
				 [9, 13, 17, 18], [10, 14, 18, 19], [11, 15, 19, 16]]

	@center_faces = [[0, 2], [0, 7], [0, 5], [6, 4], [6, 1], [6, 3]]			 
	@face_full = [[0, 1, 2, 3, 8, 9, 10, 11], 
				  [3, 0, 4, 7, 11, 15, 19, 16],
	              [0, 1, 5, 4, 8, 12, 16, 17], 
	              [4, 5, 6, 7, 12, 13, 14, 15], 
				  [1, 2, 6, 5, 9, 13, 17, 18], 
				  [2, 3, 7, 6, 10, 14, 18, 19]] 
				 
	#Cursors
	@id_cursor_none = hparam["Cursor_None"]	
	@id_cursor_arrow = hparam["Cursor_Arrow"]	
	@id_cursor_arrow_edge = hparam["Cursor_Arrow_Edge"]	
	@id_cursor_arrow_face = hparam["Cursor_Arrow_Face"]	
	@id_cursor_arrow_exit = hparam["Cursor_Arrow_Exit"]	
	@id_cursor_validate = hparam["Cursor_Validate"]	
	
	#Initialization of dynamic parameter
	@close_in_pixel = 10
	@from_center = false
	@freedof = false
	@pk_vecdir0 = nil
	@pk_normal0 = nil
	@ctrl_down = false
	@shift_down = false
	@running_target = false
	@running_protractor = false
	@running_divider = false
	@rotate_param = FSC_RotateParam.new

	#Creating the Function Key Options
	key = MYDEFPARAM[:DEFAULT_Key_LiveDeform]
	@fkey_livedeform = Traductor::FKeyOption.new(T6[:MNU_NoDeform], key) { toggle_live_deform }
	key = MYDEFPARAM[:DEFAULT_Key_Wireframe]
	@fkey_wireframe = Traductor::FKeyOption.new(T6[:MNU_Wireframe], key) { toggle_wireframe }
	key = MYDEFPARAM[:DEFAULT_Key_ShowDivider]
	@fkey_showdivider = Traductor::FKeyOption.new(T6[:MNU_DividerShowHide], key, T6[OPT_ShowDivider]) { toggle_show_divider }
	key = MYDEFPARAM[:DEFAULT_Key_ActivateDice]
	@fkey_dice = Traductor::FKeyOption.new(T6[:MNU_ActivateDice], key) { toggle_dicing }
	key = MYDEFPARAM[:DEFAULT_Key_EdgeNewProp]
	@fkey_new_edges = Traductor::FKeyOption.new(T6[:T_MNU_PropNewEdges], key) { ask_edge_new_prop }
	
	#Other parameters
	@dim_collapse_px = MYDEFPARAM[:DEFAULT_DimCollapsePixel]
end

def _select
	@model.select_tool self
end

#Initialize the behavior parameters for the tool
def init_behavior
	@color_wireframe = MYDEFPARAM[:DEFAULT_Color_Wireframe]
	
	#Calling the speicifc tool behavior
	tool__behavior
	
	#Setting live deformation
	@title_tool = F6_FredoScale.compute_title_tool @hparam	
end

#Saving parameters across FredoScale tools
def persistence_save
	@@persistence = {} unless @@persistence
	type = @hparam["OriginalToolType"]
	@@persistence[type] = {} unless @@persistence[type]
	hsh = @@persistence[type]
	
	#Parameters specific of the tool
	hsh["LiveDeform"] = @livedeform
	hsh["Wireframe"] = @wireframe
	hsh["ShowDivider"] = @show_divider
	if @mode_dice
		hsh["DiceActive"] = @dice_active
		hsh["DiceParam"] = @dice_param0
	end	
	hsh["NewEdgeProp"] = @new_edgeprop
	
	#parameters global to all tools
	@@persistence["Selection"] = @selection.to_a
	@@persistence["ScaleBox_Normal"] = (@pk_normal0) ? Geom::Vector3d.new(@pk_normal0) : nil
	@@persistence["ScaleBox_Vecdir"] = (@pk_vecdir0) ? Geom::Vector3d.new(@pk_vecdir0) : nil
end

def persistence_restore
	type = @hparam["OriginalToolType"]
	@@persistence = {} unless @@persistence
	hsh = @@persistence[type]
	unless hsh
		hsh = @@persistence[type] = {}
		hsh["LiveDeform"] = MYDEFPARAM[:DEFAULT_Flag_LiveDeform]
		hsh["Wireframe"] = MYDEFPARAM[:DEFAULT_Flag_Wireframe]
		hsh["ShowDivider"] = MYDEFPARAM[:DEFAULT_Flag_ShowDivider]
		hsh["DiceActive"] = MYDEFPARAM[:DEFAULT_Flag_ActivateDice]
		hsh["NewEdgeProp"] = MYDEFPARAM[:DEFAULT_Flag_EdgeNewProp]
	end	
	@persistence = hsh
	@livedeform = hsh["LiveDeform"]
	@wireframe = hsh["Wireframe"]
	@show_divider = hsh["ShowDivider"]
	@new_edgeprop = hsh["NewEdgeProp"]
	if @mode_dice
		@dice_active = hsh["DiceActive"]
		@dice_param0 = hsh["DiceParam"]
		@dice_param0 = @dice_param_def.clone unless @dice_param0
		compute_dice
	end	
	@livedeform = false unless @authorize_livedeform
end

#Used to switch temporarily to FredoScale mode if dimension is 1 for other tools
def disguise_tool(restore=false)
	@disguised = !restore
	originaltype = @hparam["OriginalToolType"]
	return if originaltype =~ /Scale/i
	@hparam["ToolType"] = (restore) ? originaltype : ((@type_disguise) ? @type_disguise : "Scale")
	init_behavior
	show_info
end

#Tool activation - We compute the inital Bounding box
def activate
	
	#Returning from the auxiliary Tools
	if @running_target || @running_protractor || @running_divider
		@dragging = false
		@running_target = false
		@running_protractor = false
		@running_divider = false
		@within_divider = 0
		@view.invalidate
		show_info
		onSetCursor
		return		
	end

	#Titles and messages in status bar
	@title_tool = F6_FredoScale.compute_title_tool @hparam
	@title_grip = T6[:MSG_Status_Grip]
	@title_drag = T6[:MSG_Status_Drag]
	@label_angle = (@protractor) ? T6[:T_VCB_Angle]: T6[:T_VCB_Angle_Slope]
		
	#Initialization
	@selection = @model.selection
	@operation = false
	@tr_previous = @tr_identity
	@button_down = false
	@dragging = false
	@picklinetool = nil
	@pickangletool = nil
	@label_axes = nil
	@num_handle_prev = nil
	@num_handle_last = nil
	@iaxe_last = nil
	@from_center_prev = nil
	@angle_prev = 0
	@scales = nil
	@bounds = Geom::BoundingBox.new
	@pk_edge = nil
	@pk_face = nil
	@post_validate = false
	@new_session = true
	@width_box = MYDEFPARAM[:DEFAULT_WidthBox]
	@limit_inference_scale = MYDEFPARAM[:DEFAULT_LimitInferenceScale]

	#Taking care of the selection to include edges when appropriate
	if @mode_spy 
		@selection.each { |e| @selection.add e.edges if e.class == Sketchup::Face }
	end
	
	#Compute the initial axes of selection
	@axes_of_selection = axes_of_selection
	@nb_axes_of_selection = 0
	
	#creating the scale box and doing the first calculation
	compute_initial_box
end

#Try to restore the same directions if the selection was the same
def check_selection_directions
	if @@persistence["Selection"] == @selection.to_a
		@@persistence.delete "Selection"
		@pk_normal0 = @@persistence.delete "ScaleBox_Normal"
		@pk_vecdir0 = @@persistence.delete "ScaleBox_Vecdir"
	end	
end

#Compute the initial box
def compute_initial_box
	#check_selection_directions
	@scalebox = Traductor::ScaleBox.new @selection, @pk_normal0, @pk_vecdir0
	@bbox0 = @scalebox.get_bbox
	
	#Rest of the initialization
	@hsh_entities = @scalebox.get_hsh_entities
	@hsh_entID = @scalebox.get_hsh_entID
	@hsh_vertices = @scalebox.get_hsh_vertices
	@lst_wireframe = @scalebox.get_wireframe
	return go_back_to_selection unless @bbox0
	compute_initial_orientation
	reset_bbox
	remember_selection
	@model.active_view.invalidate
	show_info
end

def get_hsh_entityID
	(@livedeform) ? @hsh_entID : nil
end

def compute_initial_orientation
	if @bbox0.length > 2
		vec1 = @bbox0[0].vector_to @bbox0[1]
		vec2 = @bbox0[1].vector_to @bbox0[2]
		@pk_normal0 = vec1 * vec2
		@pk_vecdir0 = vec1
	end	
end

def remember_selection
	return unless @mode_spy && @selection.length > 1
	@hsh_sel_faces = {}
	@selection.each do |e|
		if e.class == Sketchup::Face
			@hsh_sel_faces[e.entityID] = e
		end
	end	
	@hsh_sel_faces = nil unless @hsh_sel_faces.length > 0
end

def remind_selection
	return unless @mode_spy && @hsh_sel_faces
	lfaces = []
	n = @hsh_sel_faces.length
	@selection.each do |e|
		if e.class == Sketchup::Edge
			e.faces.each do |f|
				if @hsh_sel_faces[f.entityID]
					lfaces.push f
					break if lfaces.length == n
				end
			end	
		end
	end
	@selection.add lfaces
end

#Commit the csaling operation
def commit_operation(type=0)
	if @operation
		status = @model.commit_operation
		@operation = false
		remember_selection
		@lst_operation.push type if status
	end	
end
	
def undo_operation
	commit_operation
	Sketchup.undo
	@lst_operation.pop
end

def abort_operation
	if @operation
		@model.abort_operation
		remind_selection
		@operation = false
	end	
end
	
def start_operation(title=nil)
	G6.start_operation @model, ((title) ? title : @title_tool), true
	@operation = true
end

def reset_operation
	@lst_operation = []
end


#Deactivate the tool - Validate the scaling operation if any active
def deactivate(view)
	#Switching to the PickLineTool
	return if @running_target || @running_protractor || @running_divider
	
	#Validating the scaling operation
	@bbox = nil
	commit_operation
	persistence_save
	@model.selection.each { |e| @model.selection.add e } #if SU_MAJOR_VERSION < 6
	view.invalidate
	Sketchup.undo if @lst_operation.last == 1
end

#Return the SU bounding box for drawing limits
def getExtents
    @bounds
end

#Cancel event
def onCancel(flag, view)
	#Cancel current scaling operation if any
	if @dragging || @running_target || @running_protractor
		case flag
		when 0	#Press Escape
			if @operation
				undo_operation
			end	
			reset_to_original view
		when 1	#Reactivate the same tool
			if @operation
				undo_operation
			end	
			activate
		else	#Undo
			commit_operation
			UI.start_timer(0.1) { self.activate }
		end
		
	#User pressed Escape
	elsif flag == 0
		go_back_to_selection
		
	#real Undo	or Redo
	elsif flag == 2
		UI.start_timer(0.2) { self.activate }
	end
end

#Go back to selection tool
def go_back_to_selection
	@pk_vecdir0 = nil
	@pk_normal0 = nil	
	@selection.clear
	disguise_tool true if @disguised
	@hparam["SelectTool"]._select
end

#Reset the scaling box and selection to its original before dragging
def reset_to_original(view)
	@bbox = @bbox0.collect { |pt| pt }
	@handles = nil
	@sel_handles = nil
	@dragging = false
	@pt_target = nil
	@tr_previous = @tr_identity
	@scales = [1.0, 1.0, 1.0]
	compute_original_handles
	tool__cancel
	onMouseMove_zero if !@livedeform
end

#When view has been changed, by zooming or orbiting - recalculate the coordinates of handles
def resume(view)
	refresh_handles view
end

#Method called when a Scale box has been computed
def reset_bbox
	@bbox0 = @scalebox.get_bbox
	@bbox = @bbox0.collect { |pt| pt }
	@box_axes0 = nil
	case @bbox.length
	when 2
		@lnum = @lnumref[0..1]
		@dim = 1
	when 4
		@lnum = @lnumref[0..7]
		@dim = 2
	else
		@lnum = @lnumref
		@dim = 3
	end	
	
	#Degrading to Scale tool if dimension not big enough
	disguise_tool if @dim_disguise && @dim <= @dim_disguise
	
	#Compute the handles for the original box
	compute_original_handles
	@handles = nil
	@sel_handles = nil
	@tr_box = nil
	@tr_new = nil
	@tr_deform = nil
	@post_validate = false
	@dragging = false
	@new_session = true
	@new_orientation = true
	@pivot_divider = nil
	@pos_divider = []
	@lpt_divider = nil
	@lpt_divider_sym = nil
	@iaxe_last = nil
	
	@bbox.each { |pt| @bounds.add pt }
end

#Trap Modifier keys for extended and Keep selection
def check_function_key(key, rpt, flags, view)
	lskey = []
	lskey.push @fkey_livedeform if @authorize_livedeform
	lskey.push @fkey_wireframe
	lskey.push @fkey_showdivider if @mode_divider
	lskey.push @fkey_dice if @mode_dice
	lskey.push @fkey_new_edges if @mode_new_edges
	lskey.each do |fk|
		return true if fk.test_key(key)
	end	
	false
end

#Handle key down events
def onKeyDown(key, rpt, flags, view)
	key = Traductor.check_key key, flags, false
	
	case key			
	when CONSTRAIN_MODIFIER_KEY		#Shift key Uniform vs. non-uniform
		@shift_down = true
		change_free_uniform() unless @mode_nocenter
		return
	when COPY_MODIFIER_KEY			#Ctrl key - Center vs. Opposite
		@ctrl_down = true
		change_from_center() unless @mode_nocenter
		return
	when 13
		Traductor::ReturnUp.set_off
		return
	end

	#Function Key options
	return if check_function_key(key, rpt, flags, view)
	
	#trapping the arrow keys for imposed direction or plane
	unless @dragging
		axes = nil
		case key
		when VK_UP
			axes = [X_AXIS, Y_AXIS, Z_AXIS]
		when VK_LEFT
			axes = get_axes_of_selection -1
		when VK_RIGHT
			axes = get_axes_of_selection +1
		when VK_DOWN
			axes = [@NUL_AXIS, nil, nil]
		else
			return
		end	
		if axes
			@pk_vecdir = axes[0]
			@pk_normal = (@dim <= 2) ? nil : axes[2]
			execute_by_mode view
		end	
	end	
end

#Compute the axes of the top component of the selection, if any
def axes_of_selection
	laxes = []
	@selection.each do |e|
		if e.class == Sketchup::ComponentInstance || e.class == Sketchup::Group
			axes = [X_AXIS, Y_AXIS, Z_AXIS].collect { |v| G6::transform_vector v, e.transformation }
			next unless axes && axes[0].valid?		
			laxes.push axes unless laxes.find do |axe| 
				axes[0].parallel?(axe[0]) && axes[1].parallel?(axe[1]) && axes[2].parallel?(axe[2])
			end	
		end
	end	
	laxes = [[X_AXIS, Y_AXIS, Z_AXIS]] if laxes.length == 0
	laxes
end

#Get the next or previous axes of selection
def get_axes_of_selection(incr)
	@nb_axes_of_selection = (@nb_axes_of_selection + incr).modulo(@axes_of_selection.length)
	@axes_of_selection[@nb_axes_of_selection]	
end

#Handle key up events
def onKeyUp(key, rpt, flags, view)
	key = Traductor.check_key key, flags, true
	
	case key			
	when CONSTRAIN_MODIFIER_KEY
		@shift_down = false
	when COPY_MODIFIER_KEY
		@ctrl_down = false
		onMouseMove_zero
	when 13
		execute_by_mode view unless Traductor::ReturnUp.is_on?
	when 9							#TAB - Ask dimensions
		ask_dimensions() unless @post_validate || @mode_noask_dimensions
		ask_dice if @mode_dice
	else
		@from_center = ! @from_center if @ctrl_down
	end	
end

#Toggle between Center and opposite point
def change_from_center()
	@from_center = ! @from_center
	onMouseMove_zero
end

#Toggle between uniform and non-uniform scaling
def change_free_uniform()
	@freedof = ! @freedof
	onMouseMove_zero
end

#Validation procedure for box dimensions specified in the dialog box
def validate_proc_dimensions(dlg)
	dlg.hash_results.each do |key, svalue|
		len = Traductor.string_to_length_formula svalue
		return false unless len && len != 0
	end	
	true
end

#Display a dialog box to ask the box dimensions
def ask_dimensions()
	
	#checking validity
	#unless @sel_handles
	unless @num_handles
		UI.messagebox T6[:WARNING_SelHandleFirst]
		return
	end
	
	#Avoiding double-counting the Return key
	Traductor::ReturnUp.set_on
	
	#Creating the dialog box
	hparams = {}
	dlg = Traductor::DialogBox.new(@title_tool + ' - ' + T6[:DLG_Dim_title]) { validate_proc_dimensions dlg }
	
	#Computing the labels of the fields
	ldim = current_box_dimensions()
	laxes = []
	@iaxes.each { |i| laxes.push [i, ldim[i]] if i }
	laxes.sort! { |a, b| a[0] <=> b[0] }
	ls = laxes.collect { |a| a[1] }
	defval = []
	laxes.each_with_index do |a, i|
		j = a[0]
		len = a[1]
		sd = a[1].to_l.to_s.gsub(/\~\s*/, '')
		case @name_axes[j]
		when "Red" ; axlab = T6[:T_STR_RedAxis]
		when "Green" ; axlab = T6[:T_STR_GreenAxis]
		when "Blue" ; axlab = T6[:T_STR_BlueAxis]
		end
		label = axlab + " --> " + T6[:STR_DimFromTo, sd] + "                ~"
		defval[j] = sd
		dlg.field_string j.to_s, label, sd
	end

	#Invoking the dialog box
	hparams = dlg.show hparams
	return unless hparams
	
	if @dragging
		register_scaling
	end
	@post_validate = false
	validate_scaling true
	ldim = current_box_dimensions()	
	
	#Validating the answers
	scale = [1.0, 1.0, 1.0]
	newldim = @box_dim0.collect { |d| d }
	hparams.each do |key, svalue|
		len = Traductor.string_to_length_formula svalue
		i = key.to_i
		scale[i] = len / ldim[i] unless svalue == defval[i]
		newldim[i] = len
	end
	
	#Applying the scaling
	@scales = scale
	@num_handle_prev = nil
	@deltavec = nil
	new_session
	
	#Post Validate mode
	if @mode_postvalidate
		view = @model.active_view
		compute_box_transformation
		register_scaling
		return activate
		scale_the_box view
		@post_validate = true
		return validate_scaling(true)
	end
	
	#natural mode
	execute_scaling
	@box_dim0 = newldim

	@num_handle_prev = nil
	@scales = [1.0, 1.0, 1.0]
	validate_scaling(true)
	show_info
end

#Setting the proper cursor
def onSetCursor
	if @sel_handles
		UI::set_cursor((@dragging) ? 0 : @id_cursor_none)
	elsif @pk_edge
		UI::set_cursor @id_cursor_arrow_edge
	elsif @pk_face
		UI::set_cursor @id_cursor_arrow_face
	elsif @ip.vertex == nil && @ip.edge == nil && @ip.face == nil 
		UI::set_cursor @id_cursor_arrow_exit
	elsif @post_validate
		UI::set_cursor @id_cursor_validate
	else
		UI::set_cursor @id_cursor_arrow
	end	
end

#Contextual menu
def getMenu(menu)
	if @sel_handles && !@mode_nocenter
		menu.add_item(T6[:MNU_AskDimensions]) { ask_dimensions() } unless @post_validate || @mode_noask_dimensions
		if @dof > 1
			symb = (@freedof) ? (:MNU_UniformScaling) : (:MNU_FreeScaling)
			text = T6[symb, axes_to_nice_text]
			menu.add_item(text) { change_free_uniform() }
		end
	end	
	
	#Function key Toggling menu
	common_menu(menu)

	if @sel_handles && !@dragging && @protractor == nil && !@postvalidate
		menu.add_item(T6[:MNU_SwitchToTarget]) { switch_free_target @view }
		menu.add_separator
	end
	
	if @within_divider && @within_divider > 0
		menu.add_item(T6[:MNU_DividerDefault]) { reset_divider_to_centre }
		menu.add_separator
	end	
		
	menu.add_item(T6[:MNU_Done]) { register_scaling } if @dragging
	menu.add_item(T6[:MNU_Validate]) { validate_scaling true } if @post_validate
	menu.add_item(T6[:MNU_Exit]) { exit_tool() }
	true
end

#Common contextual menus
def common_menu(menu)
	unless @mode_nocenter
		symb = (@from_center) ? (:MNU_FromOpposite) : (:MNU_FromCenter)
		menu.add_item(T6[symb]) { change_from_center() } 
		menu.add_separator
	end
	
	@fkey_livedeform.create_menu_flag menu, @livedeform if @authorize_livedeform
	@fkey_wireframe.create_menu_flag menu, @wireframe	
	@fkey_showdivider.create_menu_flag menu, @show_divider if @mode_divider
	if @mode_dice	
		@fkey_dice.create_menu_flag menu, @dice_active
		menu.add_item(T6[:MNU_DiceParam]) { ask_dice }
	end	
	if @mode_new_edges	
		@fkey_new_edges.create_menu_flag menu, @dice_active
	end	
	menu.add_separator
end

#Invoke the dicing parameter dialog box	
def ask_dice
	status = G6::TR_Dicer.ask_dice_parameters @title_tool, @dice_param0
	Traductor::ReturnUp.set_on
	@dice_active = true if status
	compute_dice
end

#Recalculate the dicing parameters
def compute_dice
	if @dice_active
		@dice_param = @dice_param0
		@dice = G6::TR_Dicer.get_effective_dice @dice_param
	else
		@dice = 0
		@dice_param = nil
	end	
end

#Toggle live deform mode
def toggle_live_deform
	@livedeform = !@livedeform
	if @running_protractor
		@pickangletool.refresh_hsh_entityID
	elsif @running_target
		@picklinetool.refresh_hsh_entityID
	end	
	onMouseMove_zero if @dragging && @livedeform
end

#Toggle visibility of wireframe
def toggle_wireframe
	@wireframe = !@wireframe
	onMouseMove_zero if @dragging && @wireframe
end

#Toggle visibility of stretch divider
def toggle_show_divider
	if @show_divider == '0'
		@show_divider = 'G'
	elsif @show_divider == 'G'
		@show_divider = 'R'
	else
		@show_divider = '0'
	end	
	@view.invalidate
end

#Toggle activation of the slicer
def toggle_dicing
	@dice_active = !@dice_active
	compute_dice
	onMouseMove_zero
end

def ask_edge_new_prop
	@new_edgeprop = G6.ask_prop_new_edges @title_tool, @new_edgeprop
	Traductor::ReturnUp.set_on
end

#General draw method of the Tool
def draw(view)
	return unless @bbox
	
	#Synchronize draw and move
	@moving = false
	
	#Compute handles if not done already
	compute_current_handles view unless @handles
	
	#Drawing the box
	draw_box view

	#draw the dividers if required
	draw_dividers view
	
	#drawing the wireframe
	draw_wireframe view
	
	#Drawing the selected edge
	if @pk_edge
		lpt = @lpt_edge.collect { |pt| view.screen_coords pt }
		view.line_width = 4
		view.line_stipple = ""
		view.drawing_color = COLOR_Pk_Edge
		view.draw2d GL_LINES, lpt
	end	
	
	#Drawing the scaling handles
	(@mode_pads && @dim > 1) ? draw_all_pads(view) : draw_all_handles_free(view)
	draw_selected_handles view
	draw_axes view
	
	#Drawing the dicing marks
	draw_dicing_marks(view)
	
	#Drawing the dotted line
	if @dragging && @pt_target && @sel_handles && !@mode_target
		view.line_stipple = "-"
		view.line_width = 1
		view.drawing_color = color_from_vector @sel_handles[0].pt3d.vector_to(@pt_target), COLOR_Free_LineTarget
		view.draw2d GL_LINE_STRIP, @sel_handles[0].pt2d, view.screen_coords(@ip.position)
		if @use_ip
			@ip.draw view
		else	
			m = MARK_Free_Target
			view.draw_points @ip.position, m[0], m[1], m[2]
		end	
	end	
end

#Drawing the dicing marks if applicable
def draw_dicing_marks(view)
	return unless @sel_handles && @mode_dice && @protractor && @dice_param && @dice_param.nb != 0
	pt1 = @sel_handles[0].pt3d
	pt2 = @sel_handles[1].pt3d
	vec = pt1.vector_to(pt2).axes[0]
	G6::TR_Dicer.draw_dicer_marks view, @dice_param, pt1, pt2, vec, 'darkorange', 10, 3
end

#Draw the dividers if required
def draw_dividers(view)
	return unless @mode_divider && @iaxe_last && @show_divider != '0'
	n = (@dim <= 2 || @show_divider == 'G') ? -1 : 7
	ldim = @box_dim0
	color = ['Red', 'Green', 'Blue'][@iaxe_last]
	view.drawing_color = color
	view.line_width = (@within_divider == 1) ? 4 : 3
	view.line_stipple = (@within_divider == 1) ? "" : '-'
	view.draw GL_LINES, @lpt_divider[0..n]
	
	if @from_center && @lpt_divider_sym
		lpt2d = @lpt_divider_sym.collect { |pt| view.screen_coords pt }
		view.line_width = (@within_divider == 2) ? 4 : 3
		view.line_stipple = (@within_divider == 2) ? "" : '-'
		view.draw GL_LINES, @lpt_divider_sym[0..n]
	end	
	
	polydiv = (@within_divider == 0) ? nil : ((@within_divider == 1) ? @poly_divider : ((@from_center) ? @poly_divider_sym : nil))
	if polydiv
		view.drawing_color = 'yellow'
		view.line_width = 1
		view.line_stipple = '-'
		view.draw GL_POLYGON, polydiv
	end
end

#Draw the wireframe when applicable
def draw_wireframe(view)
	return unless @wireframe && @dragging && !@livedeform
	pts = compute_wireframe
	return unless pts.length > 1
	view.line_stipple = ""
	view.line_width = 1
	view.drawing_color = @color_wireframe
	pts2 = pts.collect { |pt| view.screen_coords(pt) }
	view.draw2d GL_LINES, pts2
end
	
#Draw the scaling box
def draw_box(view)
	view.drawing_color = (@mode_target) ? @color_box_target : @color_box
	if @mode_target
		view.line_width = @width_box
		view.line_stipple = ""
		lpt3d = @lnum.collect { |i| small_offset view, @bbox[i] }
		#lpt2d = @lnum.collect { |i| view.screen_coords @bbox[i] }
		#view.draw2d GL_LINES, lpt2d	
		view.draw GL_LINES, lpt3d	
	elsif @dragging || @running_divider
		view.line_width = 3
		view.line_stipple = "-"
		lpt2d = @lnum.collect { |i| view.screen_coords @bbox[i] }
		view.draw2d GL_LINES, lpt2d	
	else
		view.line_width = @width_box
		view.line_stipple = ""
		lpt3d = @lnum.collect { |i| small_offset view, @bbox[i] }
		view.draw GL_LINES, lpt3d	
	end	
end

#Calculate the point slightly offset to cover the edges
def small_offset(view, pt)
	pt2d = view.screen_coords pt
	ray = view.pickray pt2d.x, pt2d.y
	vec = ray[1]
	size = view.pixels_to_model 1, pt
	pt.offset vec, -size
end

#Determine the color of a line, based on the vector direction
def color_from_vector(vec, def_color)
	return def_color unless vec.valid?
	if vec.parallel?(X_AXIS)
		return "red"
	elsif vec.parallel?(Y_AXIS)
		return "green"
	elsif vec.parallel?(Z_AXIS)
		return "blue"
	end
	def_color
end

#Check whether the handle should be included in a procedural loop
def skip_handle?(handle)
	@skip_handles[@dim-1].include?(handle.type)
end

#Drawing handles for Free Scaling
def draw_all_handles_free(view)

	#Drawing all handles
	view.line_stipple = ""
	view.line_width = 1
	@handles.each do |handle| 
		next if skip_handle?(handle)
		next if handle.unusable
		next if !handle.lquads || handle.lquads.empty?
		view.drawing_color = "black"
		view.draw GL_QUADS, handle.lquads
		view.drawing_color = "lightgreen"
		view.draw GL_LINES, handle.lbox 
	end	
	
	#Drawing the center if selected
	if @from_center && @sel_handles == nil
		#handle = @handles.last
		handle = @hcenter
		view.drawing_color = COLOR_Sel_Handle_Pivot
		view.draw2d GL_QUADS, handle.lquads2d
		view.drawing_color = "black"
		view.draw2d GL_LINES, handle.lbox2d
	end
end

#Drawing pads
def draw_all_pads(view)
	#Highlighting the selected face if any
	if @highlight && (@sel_handles || @post_validate)
		lface = (@dim == 2) ? [@highlight.ih, @highlight.oih] : @face_full[@highlight.iface][0..3]
		lpt2d = lface.collect { |i| @handles[i].pt2d }
		lpt2d += [lpt2d.first]
		view.line_width = 3
		view.line_stipple = ""
		view.drawing_color = "red"
		view.draw2d GL_LINE_STRIP, lpt2d	
	end
	
	#Do not draw the pads if dragging
	return if @dragging || @post_validate
	
	#Drawing all handles
	view.line_stipple = ""
	view.line_width = 1
	@handles.each do |handle| 
		next if skip_handle?(handle)
		next if @highlight && @sel_handles && (@highlight.ih == handle.ih || @highlight.oih == handle.ih)
		view.drawing_color = "black"
		view.draw GL_QUADS, handle.lpads
		view.drawing_color = "red"
		view.draw GL_LINES, handle.lpadlines
	end	
end

#Drawing method for the selected handles
def draw_selected_handles(view)
	return unless @sel_handles
	
	#Drawing the line between handles
	handle1 = @handles[@highlight.ih]
	handle2 = (@from_center) ? @hcenter : @handles[@highlight.oih]
	view.line_stipple = "-"
	view.line_width = 2
	view.drawing_color = "black"
	view.draw2d GL_LINE_STRIP, view.screen_coords(handle1.pt3d), view.screen_coords(handle2.pt3d)
	
	#Drawing the handles selected, if any
	view.line_stipple = ""
	view.line_width = 1	
	
	hcolor = (@mode_target) ? COLOR_Sel_Handle_FreeTarget : COLOR_Sel_Handle_Free
	view.drawing_color = hcolor
	view.draw2d GL_QUADS, handle1.lquads2d unless !handle1.lquads2d || handle1.lquads2d.empty?
	view.drawing_color = "black"
	view.draw2d GL_LINES, handle1.lbox2d unless !handle1.lbox2d || handle1.lbox2d.empty?
	
	view.drawing_color = COLOR_Sel_Handle_Pivot
	view.draw2d GL_QUADS, handle2.lquads2d unless !handle2.lquads2d || handle2.lquads2d.empty?
	view.drawing_color = "black"
	view.draw2d GL_LINES, handle2.lbox2d unless !handle2.lbox2d || handle2.lbox2d.empty?
	
	#Drawing the protractor
	@protractor.draw view, 0.5 if @protractor && !@running_protractor
end

#Draw the small axes at the selected handle
def draw_axes(view)
	return unless @sel_handles #&& !@mode_target
	nbpixel = 40
	handle1 = @sel_handles[0]
	handle2 = @sel_handles[1]
	pth = handle1.pt3d
	vec = handle2.pt3d.vector_to pth
	colors = ["red", "green", "blue"]
	size_axe = view.pixels_to_model nbpixel, pth
	size_dir = size_axe * 0.5
	
	#Drawing the arrow direction
	if vec.valid?
		pt1 = pth
		pt2 = pth.offset vec, size_dir
		view.line_stipple = ""
		view.line_width = 2
		view.drawing_color = "purple"
		view.draw2d GL_LINE_STRIP, view.screen_coords(pt1), view.screen_coords(pt2)
	end	
	
	#Drawing the axes if required
	view.line_stipple = ""
	view.line_width = (@freedof && @dof > 1) ? 4 : 2
	@haxes.each_with_index do |v, i|
		next unless v && v.valid?
		pt2 = pth.offset v, size_axe
		view.drawing_color = colors[@iaxes[i]]
		view.draw2d GL_LINE_STRIP, view.screen_coords(pth), view.screen_coords(pt2)
	end
end

#Calculate the maximum degree of freedom for the handle to be moved
def degree_of_freedom
	return unless @sel_handles
	
	#evaluating the degrees of freedom
	handle1 = @sel_handles0[0]
	handle2 = @sel_handles0[1]
	case @dim
	when 1
		@dof = 1
	when 2
		@dof = (handle1.type == 'B') ? 2 : 1
	else
		['F', 'M', 'B'].each_with_index { |type, i| @dof = i + 1 if type == handle1.type }
	end			
	
	#Decrease by 1 in Pads mode
	@dof = @dof - 1 if @mode_pads && @dof > 1
	
	#Finding the axes to the direction
	@vecdir = handle2.pt3d.vector_to handle1.pt3d
	vecdir = handle2.pt3d.vector_to handle1.pt3d
	@iaxes = [nil, nil, nil]
	case @dof
	when 1
		@box_axes.each_with_index do |v, i|
			if vector_colinear?(vecdir, v)
				@iaxes[0] = i
				break
			end
		end	
	when 2
		@box_axes.each_with_index do |v, i|
			if (vecdir % v).abs < 0.0001
				@iaxes[0] = (i+1).modulo(3)
				@iaxes[1] = (i+2).modulo(3)
				@normaldof2 = v
				break
			end
		end	
	when 3
		@iaxes = [0, 1, 2]
	end
	@haxes = @iaxes.collect { |iv| (iv) ? @box_axes[iv] : nil }
	@haxes.collect! { |v| (v && @vecdir % v < 0) ? v.reverse : v }	
	
	#Compute the label for VCB and tooltip
	laxes = []
	@iaxes.each { |i| laxes.push i if i }
	laxes.sort!		
	laxcol = laxes.collect { |i| @name_axes[i] }
	@label_axes = laxcol.join " "
end

#Check if 2 vectors are colinear with some tolerance
def vector_colinear?(v1, v2, tolerance=nil)
	tolerance = 0.0001 unless tolerance
	ps = v1.normalize % v2.normalize
	return ((ps.abs - 1.0).abs < tolerance)
end

#Check Inference for input point - Exclude inference when positioned on a vertex or edge of the selection
def valid_inference?(ip, view)
	dof = ip.degrees_of_freedom
	return false if dof > 1
	[/from/i, /von/i, /de/i].each do |pat|
		return false if ip.tooltip && ip.tooltip =~ pat
	end	
	v = ip.vertex
	e = ip.edge
	f = ip.face
	if v || e
		ph = view.pick_helper
		pt2d = view.screen_coords ip.position
		ph.do_pick pt2d.x, pt2d.y
		best = ph.best_picked
		return false if best && @hsh_entities[best.entityID]
	end	
	if @hsh_entID
		return false if v && @hsh_entID[v.entityID]
		return false if e && @hsh_entID[e.entityID]
		return false if f && @hsh_entID[f.entityID]
	end
	
	true	
end

#Check if the mouse is on an inference point
def check_for_input_point(view, x, y)
	@ip.pick view, x, y
	if valid_inference?(@ip, view) && (@dof == 1 || @freedof)
		@use_ip = true
		return @ip.position
	end	
	@use_ip = false
	return nil
end

#Compute the target points when dragging, based on the mouse coordinates
def compute_target_point(view, x, y)
	#Calculating the actual points on the direction vector
	@deltavec = nil
	@pt_origin = @sel_handles0[0].pt3d ####
	if @dof == 1 || @freedof == false
		#vec3d = @sel_handles[1].pt3d.vector_to @sel_handles[0].pt3d
		vec3d = @sel_handles0[1].pt3d.vector_to @sel_handles0[0].pt3d
		pth3d = @pt_origin
		pt3d = check_for_input_point view, x, y
		if pt3d
			@pt_target = Geom.intersect_line_plane [pth3d, vec3d], [pt3d, vec3d]
		else
			vec3d = Z_AXIS unless vec3d.valid?
			ptoff3d = pth3d.offset vec3d, 1
			pth2d = view.screen_coords pth3d
			ptoff2d = view.screen_coords ptoff3d
			ptoff2d.z = pth2d.z = 0
			vec2d = pth2d.vector_to ptoff2d
			ptxy = Geom::Point3d.new x, y, 0
			pt2d = ptxy.project_to_line [pth2d, vec2d]
			lpt = Geom.closest_points [pth3d, vec3d], view.pickray(pt2d.x, pt2d.y)
			@pt_target = lpt[0]
		end	
	elsif @dof == 2
		plane = [@pt_origin, @normaldof2]
		vec3d = @sel_handles[1].pt3d.vector_to @sel_handles[0].pt3d
		pth3d = @pt_origin
		pt3d = check_for_input_point view, x, y
		if pt3d
			@pt_target = pt3d.project_to_plane plane
		else	
			ptxy = Geom::Point3d.new x, y, 0
			@pt_target = Geom.intersect_line_plane view.pickray(ptxy.x, ptxy.y), plane
		end	
	else
		@ip.pick view, x, y	
		@pt_target = @ip.position
		@use_ip = true
	end
end

#Create a single handle structure
def create_one_handle(pt, type, ih, dimside=nil)
	handle = FSC_Handle.new
	handle.type = type
	handle.pt3d = pt
	handle.dimside = dimside
	find_opposite_handle handle, ih
	find_other_handle_pads(handle, ih) if @mode_pads
	handle
end

#Refresh the drawing of a handle when view is changed, to keep it the same size
def refresh_handles(view)
	@handles = compute_handles @bbox unless @handles
	compute_handle_unusable view, @handles, @bbox
	
	#Handles in quad mode
	nbpixel = 5
	(@handles).each do |handle|
		next unless handle
		pt = handle.pt3d
		size = view.pixels_to_model nbpixel, pt
		next if @box_axes.find { |v| !v.valid? }
		t = Geom::Transformation.axes(pt, @box_axes[0], @box_axes[1], @box_axes[2]) * Geom::Transformation.scaling(size)
		lhpt = @xyz_handle.collect { |ptxyz| t * ptxyz }
		handle.pt2d = view.screen_coords pt
		handle.lbox = @lnumref.collect { |i| lhpt[i] }
		handle.lbox2d = handle.lbox.collect { |pt| view.screen_coords pt }
		handle.lquads = @qnumref.collect { |i| lhpt[i] }
		handle.lquads2d = handle.lquads.collect { |pt| view.screen_coords pt }
	end	
	precompute_divider if @mode_divider
	return unless @mode_pads
	
	#Handles as long pad (Pads mode only)
	@handles.each do |handle|
		next if skip_handle?(handle)
		handle.lpads = []
		handle.lpadlines = []
		handle.lpadzone = []
		handle.oih.each do |oih|
			(@dim == 2) ? build_pad2d(view, handle, oih) : build_pad3d(view, handle, oih)
		end	
	end
	
end

#Build the selection pad for a 2D selection
def build_pad2d(view, handle, oih)
	ih = handle.ih
	pt3d = handle.pt3d
	axes = []
	axes[0] = pt3d.vector_to @handles[oih].pt3d
	
	#Corner handle
	axes[1] = @handles[handle.opp].pt3d.vector_to @handles[oih].pt3d
	decx = 6
	decy = 2
	bb = [[0, 0], [decx, 0], [decx, decy], [0, decy]]
	axes[2] = axes[0] * axes[1]
	
	#Computing the coordinate of the pad
	nbpixel = 3
	size = view.pixels_to_model nbpixel, pt3d
	t = Geom::Transformation.axes(pt3d, axes[0], axes[1], axes[2]) * 
		Geom::Transformation.scaling(size)
		
	bbz = bb.collect { |a| Geom::Point3d.new a[0], a[1], 0 } 
	bb = bb.collect { |a| Geom::Point3d.new a[0], a[1], -1 } + 
		 bb.collect { |a| Geom::Point3d.new a[0], a[1], 1 }

	lhpt = bb.collect { |ptxyz| t * ptxyz }
	handle.lpadlines += @lnumref.collect { |i| lhpt[i] }
	handle.lpads += @qnumref.collect { |i| lhpt[i] }
	hl = create_highlight handle.ih, oih
	handle.lpadzone.push [hl, [pt3d, pt3d.offset(axes[0], decx * size)]]
end

#Build the selection pad for a 3D selection
def build_pad3d(view, handle, oih)
	#finding the face
	lh = [handle.ih, oih]
	lface = nil
	@face_ref.each do |lf| 
		if (lf & lh).length == 2 
			lface = lf
			break
		end
	end
	return unless lface
		
	#Computing the directions
	pth = handle.pt3d
	vopp = @handles[handle.opp].pt3d.vector_to pth
	size = view.pixels_to_model 6, pth
	pth = secure_offset pth, vopp, size
	nbpixel = 15
	size = view.pixels_to_model nbpixel, pth
	vech = pth.vector_to @handles[oih].pt3d
	
	#Corner handle
	if handle.type == 'M'
		ooih = lface - lh
		vec2 = @handles[ooih[0]].pt3d.vector_to @handles[ooih[1]].pt3d
		vec2 = vec2.reverse unless vec2 % vech > 0
		pt1 = secure_offset pth, vec2, -size * 0.5
		pt2 = secure_offset pt1, vech, size
		pt3 = secure_offset pt2, vec2, size
		pt4 = secure_offset pt3, vech, -size		
	elsif handle.type == 'B'
		vec1 = @handles[lface[0]].pt3d.vector_to @handles[lface[1]].pt3d
		vec1 = vec1.reverse unless vec1 % vech > 0
		vec2 = @handles[lface[1]].pt3d.vector_to @handles[lface[2]].pt3d
		vec2 = vec2.reverse unless vec2 % vech > 0
		pt1 = pth
		pt2 = secure_offset pt1, vec1, size
		pt3 = secure_offset pt2, vec2, size
		pt4 = secure_offset pt3, vec1, -size
	end
	
	pad = [pt1, pt2, pt3, pt4]
	handle.lpads += pad
	handle.lpadlines.push pad[0], pad[1], pad[1], pad[2], pad[2], pad[3], pad[3], pad[0]
	hl = create_highlight handle.ih, oih
	handle.lpadzone.push [hl, pad]
end

def secure_offset(pt, vec, size)
	return pt.clone unless vec.valid?
	pt.offset vec, size
end

#Modify the box axes to try fitting the SU axes
def match_SU_axes
	lst_axes = [X_AXIS, Y_AXIS, Z_AXIS]
	axes = [@box_axes[0], @box_axes[1], @box_axes[2]]
	@jaxe = [0, 1, 2]
	ldim = box_original_dimensions()
	lst_axes.each_with_index do |axis, ix|
		axes.each_with_index do |v, i|
			next unless v.valid?
			if v.parallel? axis
				vv = v.clone
				axes[i] = axes[ix]
				axes[ix] = vv
				d = ldim[i]
				ldim[i] = ldim[ix]
				ldim[ix] = d
				k = @jaxe[i]
				@jaxe[i] = @jaxe[ix]
				@jaxe[ix] = k
				break
			end	
		end	
	end	
	for i in 0..2
		@box_axes[i] = axes[i] if axes[i].valid?
	end	
	@box_axes0 = @box_axes.collect { |v| v.clone }
	@box_dim0 = ldim
	@box_dim = ldim.collect { |x| x }
end

#Compute the original box dimensions
def box_original_dimensions
	bbox = @bbox0
	ldim = [0, 0, 0]
	ldim[0] = bbox[0].distance bbox[1] if @dim > 0
	ldim[1] = bbox[1].distance bbox[2] if @dim > 1
	ldim[2] = bbox[0].distance bbox[4] if @dim > 2
	return ldim
end

#Return the current box dimensions
def current_box_dimensions
	ldim = [0, 0, 0]
	ldim[0] = @bbox[0].distance @bbox[1] if @dim > 0
	ldim[1] = @bbox[1].distance @bbox[2] if @dim > 1
	ldim[2] = @bbox[0].distance @bbox[4] if @dim > 2
	ldim2 = []
	@jaxe.each_with_index { |j, i| ldim2[i] = ldim[j] }
	ldim2
end

#Return the current box dimensions
def bbox_dimensions(bbox)
	ldim = []
	ldim[0] = bbox[0].distance bbox[1] if bbox[1]
	ldim[1] = bbox[1].distance bbox[2] if bbox[2]
	ldim[2] = bbox[0].distance bbox[4] if bbox[4]
	ldim
end

def compute_center
	#Free Scale mode
	if !@mode_pads || @dim == 1
		@hcenter = @handles.last
		@hcenter.pt3d = @handles0.last.pt3d
		return
	end	
	
	#Pads Mode but no handles selected yet
	unless @sel_handles	
		@hcenter = nil
		return
	end
	
	#Handle selected - Computing the corresponding center handle
	@hcenter = @handles[@highlight.ihcenter]
end

#Compute the position of the handles of the original box
def compute_original_handles
	return unless @bbox
	
	#Compute the handles position for the original box
	@handles0 = compute_handles @bbox0
	
	#Computing the axes of the box and adjusting the axes to match the SU model axes if possible, unless already done
	if @box_axes0 
		@box_axes = @box_axes0.collect { |v| v.clone }
	else	
		vecx = @bbox0[0].vector_to @bbox0[1]
		if (@dim == 1)
			vecy = vecx.axes[0]
		else
			vecy = @bbox0[1].vector_to(@bbox0[2])
		end	
		@box_axes = [vecx, vecy, vecx * vecy]
		match_SU_axes
	end	
end
		
#Compute the handles for the current box
def compute_current_handles(view)
	@handles = compute_handles @bbox
	refresh_handles view
	compute_center
end

#Compute the handles for a given box
def compute_handles(bbox)
	return unless bbox
	handles = []
	nh = -1
		
	#Handle for the box itself
	bbox.each do |pt|
		handles.push create_one_handle(pt, 'B', nh += 1)
	end	

	#Handle in the middle of edges (2D and 3D)
	if @dim >= 2
		n = @lnum.length / 2 - 1
		for i in 0..n
			pt1 = bbox[@lnum[2 * i]]
			pt2 = bbox[@lnum[2 * i + 1]]
			ptmid = Geom::Point3d.linear_combination 0.5, pt1, 0.5, pt2
			handles.push create_one_handle(ptmid, 'M', nh += 1, pt1.distance(pt2))
		end
	end

	#Handle in the middle of faces (3D only)
	if @dim > 2
		@center_faces.each do |ln|
			pt1 = bbox[ln[0]]
			pt2 = bbox[ln[1]]
			ptface = Geom::Point3d.linear_combination 0.5, pt1, 0.5, pt2
			handles.push create_one_handle(ptface, 'F', nh += 1)
		end
	end
	
	#handle in the center
	pt1 = bbox[@lnumcenter[@dim][0]]
	pt2 = bbox[@lnumcenter[@dim][1]]
	ptcenter = Geom::Point3d.linear_combination 0.5, pt1, 0.5, pt2
	handles.push create_one_handle(ptcenter, 'C', nh += 1)
	
	return handles
end

def compute_handle_unusable(view, handles, bbox)
	return if @dim < 2
	ldim = bbox_dimensions bbox
	pix = @dim_collapse_px
	size = view.pixels_to_model pix, bbox[0]
	idim = nil
	uldim = ldim.find_all { |a| a < size }
	nuldim = (uldim.length > 0)
	
	#Box has the required size
	unless nuldim
		handles.each { |h| h.unusable = false }
		return
	end
	
	#At least one dimension is too small
	handles.each do |handle|
		if handle.type == 'B'
			size = view.pixels_to_model pix, handle.pt3d
			uldim = ldim.find_all { |a| a < size }
			handle.unusable = (uldim.length > 0)
		elsif handle.type == 'M'
			size = view.pixels_to_model pix, handle.pt3d
			uldim = ldim.find_all { |a| a < size }
			handle.unusable = (uldim.length > 0 && handle.dimside > size)
		end
	end	
end

#Check if the mouse is close to a particular handle. If so, we also determine the pairing handle.
def mouse_within_handle(view, x, y)
	#Post_validation case
	lst_handles = (@post_validate) ? [@handles[@num_handles[0]]] : @handles
	
	#Finding the handle close to the mouse location, if any
	ptxy = Geom::Point3d.new x, y, 0
	lsth = []
	
	#Pads mode in 3D: locating the handle via its pads
	if @mode_pads && @dim > 2
		lst_handles.each do |handle|
			next if skip_handle?(handle)
			handle.lpadzone.each do |zone|
				lpt = zone[1].collect { |pt| view.screen_coords(pt) }
				if Geom.point_in_polygon_2D(ptxy, lpt, true)
					lsth.push [zone[0], view.camera.eye.distance(zone[1][2])] 
				end
			end	
		end

	#Pads mode in 2D: locating the handle via radial proximity
	elsif @mode_pads && @dim > 1
		lst_handles.each do |handle|
			next if skip_handle?(handle)
			handle.lpadzone.each do |zone|
				lpt = zone[1].collect { |pt| view.screen_coords(pt) }
				pt1 = lpt[0]
				pt2 = lpt[1]
				ptproj = ptxy.project_to_line [pt1, pt2]
				next unless ptproj
				d = ptproj.distance ptxy
				if (d <= 10) && (ptproj.vector_to(pt1) % ptproj.vector_to(pt2) <= 0)
					lsth.push [zone[0], d] 
				end
			end	
		end
		
	#Free mode: just using a proximity criteria
	else
		lst_handles.each do |handle|
			next if skip_handle?(handle)
			next if handle.unusable
			pt3d = handle.pt3d
			pt2d = view.screen_coords handle.pt3d
			hl = create_highlight handle.ih, handle.opp
			lsth.push [hl, view.camera.eye.distance(pt3d)] if ptxy.distance(pt2d) <= 10
		end
	end
	
	#No handle selected
	if lsth.length == 0
		@sel_handles = nil
		compute_center
		return false
	end	
	lsth.sort! { |a, b| a[1] <=> b[1] }
	@highlight = lsth[0][0]
	ih = @highlight.ih
	ih2 = @highlight.oih
	if @post_validate && ih2 != @num_handles[1]
		ih2 = @num_handles[1]
		@highlight = create_highlight ih, ih2
	end	
	compute_center
	@within_divider = 0
	
	#Already selected
	return true if @sel_handles && @num_handles[0] == ih && @num_handles[1] == ih2
	
	#Storing the results
	@num_handles = [ih, ih2]
	@sel_handles = [@handles[ih], @handles[ih2]]
	@sel_handles0 = [@handles0[ih], @handles0[ih2]]
	@num_handle_last = [ih, ih2]
	@pt_origin = @handles0[ih].pt3d

	compute_center
	degree_of_freedom()
	@scales = [1.0, 1.0, 1.0] unless @scales
	precompute_divider if @mode_divider
		
	#Compute the protractor if any
	if @protractor
		lv = compute_protractor_at_handle
		@protractor.set_placement lv[0], lv[1], lv[2] if lv
	end	
end

#determine if the mouse pointer is within the divider(s)
def mouse_within_divider(view, x, y)
	precision = 8
	ray = view.pickray x, y
	axe = @box_axes[@iaxes[0]]
	llpt = [@lpt_divider]
	llpt.push @lpt_divider_sym if @from_center && @lpt_divider_sym
	n = (@dim <= 2 || @show_divider == 'G') ? -1 : 7
	
	llpt.each_with_index do |lpt, idiv|
		if @dim == 3
			ptplane = Geom.intersect_line_plane ray, [lpt[0], axe]
		else
			ll = Geom.closest_points ray, [lpt[0], lpt[1]]
			ptplane = ll[0]
		end
		size = view.pixels_to_model precision, ptplane
		len = lpt.length / 2 - 1
		len = n if n != -1
		for i in 0..len
			pt = is_within_seg?(ptplane, lpt[2 * i], lpt[2 * i + 1], size)
			if pt && check_point_in_front(view, pt, x, y)
				@pt3d_divider = pt
				@within_divider = idiv + 1
				return true
			end	
		end
	end
	
	@within_divider = 0	
	false
end

#Check if a mouse location allows point pt to be in the front
def check_point_in_front(view, pt, x, y)
	ip = Sketchup::InputPoint.new
	ip.pick view, x, y
	return true if ip.degrees_of_freedom >= 3
	ray = view.pickray x, y
	vec = pt.vector_to ip.position
	return false unless vec.valid?
	(vec % ray[1] >= 0)
end

#Check if a point is within a segment defined by 2 points, with a priximity limit
def is_within_seg?(pt, pt1, pt2, prox)
	return nil unless pt && pt1 && pt2
	return pt1 if pt.distance(pt1) < prox
	return pt2 if pt.distance(pt2) < prox
	ptproj = pt.project_to_line [pt1, pt2]
	return nil if pt.distance(ptproj) > prox || (pt1.vector_to(ptproj) % pt2.vector_to(ptproj) >= 0)
	ptproj
end

#Compute and check the factor for divider (pt_target = nil, means center)
def compute_divider_factor(pt_target)
	iaxe = @iaxes[0]
	daxe = current_box_dimensions[iaxe] * 0.5
	normal = @box_axes[iaxe]
	#ptpivot = (@sel_handles) ? Geom.linear_combination(0.5, @sel_handles[0].pt3d, 0.5, @sel_handles[1].pt3d) : @hcenter.pt3d
	ptpivot = Geom.linear_combination 0.5, @handles[@num_handles[0]].pt3d, 0.5, @handles[@num_handles[1]].pt3d
	
	if pt_target
		d = pt_target.distance_to_plane [ptpivot, normal]
		return nil if d > daxe * 0.99
		ptproj = pt_target.project_to_plane [ptpivot, normal]
		vec = ptproj.vector_to pt_target
		d = -d if vec.valid? && vec % normal < 0
	else
		d = 0
	end
	
	@pivot_divider[iaxe] = d
	@pos_divider[iaxe] = ptpivot.offset normal, d
	@new_orientation = true
	precompute_divider
	#precompute_divider
	return pt_target		
end

#Reset the divider to the centre
def reset_divider_to_centre
	compute_divider_factor nil
	self._select if @running_divider
end

#Precompute the divider position
def precompute_divider
	return unless @iaxes
	iaxe = @iaxe_last = @iaxes[0]
	iothers = [0, 1, 2] - [iaxe]
	axe = @box_axes[@iaxe_last]
	nbpixel = 30
	ldim = current_box_dimensions
	if @num_handles && @handles
		ptpivot = Geom.linear_combination 0.5, @handles[@num_handles[0]].pt3d, 0.5, @handles[@num_handles[1]].pt3d
	else
		ptpivot = @handles.last.pt3d
	end	
	len = ldim[iaxe] * 0.5
	@pivot_divider = [0, 0, 0] unless @pivot_divider
	unless @pos_divider[iaxe]
		@pos_divider = [ptpivot, ptpivot, ptpivot]
	end	
	ptdiv = @pos_divider[iaxe]
	@origin_divider = ptdiv
	vsize = @view.pixels_to_model nbpixel, ptdiv
	gsize = @view.pixels_to_model 40, ptdiv
	ptcenter = ptpivot.project_to_plane [ptdiv, axe]
	@lpt_divider = []
	@poly_divider = nil
	@poly_divider_sym = nil
	if @dim == 1
		axe2 = axe.axes[0]
		@lpt_divider.push ptcenter.offset(axe2, vsize), ptcenter.offset(axe2, -vsize)
	elsif @dim == 2
		iaxe2 = iothers.find { |j| @box_dim0[j] > 0 }
		axe2 = @box_axes[iaxe2]
		size = ldim[iaxe2] * 0.5 + vsize
		@lpt_divider.push ptcenter.offset(axe2, size), ptcenter.offset(axe2, -size)
	else
		@poly_divider = []
		iaxe2 = iothers[0]
		iaxe3 = iothers[1]
		axe2 = @box_axes[iaxe2]
		axe3 = @box_axes[iaxe3]
		size2 = ldim[iaxe2] * 0.5 + vsize
		size3 = ldim[iaxe3] * 0.5 + vsize
		pt = ptcenter.offset axe2, size2
		pt1 = pt.offset axe3, size3
		pt2 = pt1.offset axe2, -size2 * 2
		pt3 = pt2.offset axe3, -size3 * 2
		pt4 = pt3.offset axe2, size2 * 2
		@poly_divider.push pt1, pt2, pt3, pt4	
		@lpt_divider.push pt1, pt2, pt2, pt3, pt3, pt4, pt4, pt1 	
		ng2 = (size2 / gsize).ceil
		ng3 = (size3 / gsize).ceil
		h3 = size3 * 2 / ng3
		h2 = size2 * 2 / ng2
		for i in 1..ng3-1
			@lpt_divider.push pt1.offset(axe3, -h3 * i), pt2.offset(axe3, -h3 * i)
		end	
		for i in 1..ng2-1
			@lpt_divider.push pt2.offset(axe2, h2 * i), pt3.offset(axe2, h2 * i)
		end	
	end
	
	#Computing the symetric
	if @pivot_divider[iaxe] == 0
		@lpt_divider_sym = nil
		@poly_divider_sym = nil
	else
		tx = Geom::Transformation.axes ptpivot, @box_axes[0], @box_axes[1], @box_axes[2]
		ts = Geom::Transformation.scaling -1, -1, -1
		t = tx * ts * tx.inverse
		@lpt_divider_sym = @lpt_divider.collect { |pt| t * pt }
		@poly_divider_sym = @poly_divider.collect { |pt| t * pt } if @poly_divider
	end	
end

#Create a structure to keep hold of the handle selection for Pads mode
def create_highlight(ih, oih)
	highlight = FSC_Hilight.new
	highlight.ih = ih
	highlight.oih = oih
	
	#Finding the full face
	iface = -1
	@face_full.each_with_index do |lf, i| 
		if (lf & [ih, oih]).length == 2 
			iface = i
			break
		end
	end
	highlight.iface = iface
	
	#finding the center
	if iface >= 0
		if (@dim == 3)
			highlight.ihcenter = 20 + iface
		else
			highlight.ihcenter = ((ih+1).modulo(4) == oih) ? ih + 4 : oih + 4
		end	
	end
	highlight
end

def find_opposite_handle(handle, ih)
	handle.ih = ih
	if @dim == 1
		ih2 = (ih + 1).modulo(2)
	elsif @dim == 2
		if ih < 4
			ih2 = (ih + 2).modulo(4)
		else
			ih2 = (ih - 2).modulo(4) + 4
		end	
	else
		if (ih < 4)
			ih2 = (ih + 6).modulo(4) + 4
		elsif (ih < 8)
			ih2 = (ih + 6).modulo(4)
		elsif (ih < 12)
			ih2 = (ih + 6).modulo(4) + 12
		elsif (ih < 16)
			ih2 = (ih + 6).modulo(4) + 8
		elsif (ih < 20)
			ih2 = (ih + 2).modulo(4) + 16
		else
			ih2 = (ih - 17).modulo(6) + 20
		end	
	end
	handle.opp = ih2
end

def find_other_handle_pads(handle, ih)
	handle.ih = ih
	ih3 = ih4 = nil
	if @dim == 1
		ih2 = (ih + 1).modulo(2)
	elsif @dim == 2
		if ih < 4
			ih2 = (ih + 1).modulo(4)
			ih3 = (ih + 3).modulo(4)
		else
			ih2 = (ih - 2).modulo(4) + 4
		end	
	else
		if (ih < 4)
			ih2 = (ih + 2).modulo(4)
			ih3 = (ih + 1).modulo(4) + 4
			ih4 = (ih + 3).modulo(4) + 4
		elsif (ih < 8)
			ih2 = (ih + 2).modulo(4) + 4
			ih3 = (ih + 1).modulo(4)
			ih4 = (ih + 3).modulo(4)
		elsif (ih < 12)
			ih2 = (ih + 4).modulo(4) + 12
			ih3 = (ih + 2).modulo(4) + 8
		elsif (ih < 16)
			ih2 = (ih + 4).modulo(4) + 8
			ih3 = (ih + 2).modulo(4) + 12
		elsif (ih < 20)
			ih2 = (ih + 1).modulo(4) + 16
			ih3 = (ih - 1).modulo(4) + 16
		else
			ih2 = (ih - 17).modulo(6) + 20
		end	
	end
	
	lh = [ih2]
	lh.push ih3 if ih3
	lh.push ih4 if ih4
	handle.oih = lh
end

#Compute the scale factors from the given target point
def compute_scale_factors
	#Transformation into SU Axes
	origin = @tr_box_inv * @pt_origin
	target = @tr_box_inv * @pt_target
	tolerance = @limit_inference_scale
	minimum = LIMIT_SmallScale
	#no_rounding = false
	no_rounding = @mode_target
	
	#delta mode (stretch)
	if @mode_delta && @deltavec
		vec = origin.vector_to target
		if vec.length > 0
			origin = @tr_box_inv * @sel_handles0[0].pt3d
			target = origin.offset vec, @deltavec.length
			no_rounding = true
		end	
	end	
	
	#Calculating the scaling factors
	vori = [origin.x, origin.y, origin.z]
	vtar = [target.x, target.y, target.z]
	lscales = []
	for i in 0..2
		if (vtar[i] == vori[i])
			scale = 1.0 
		else
			return false if vori[i] == 0.0 
			scale = vtar[i] / vori[i]
		end	
		if (scale >= 0 && scale <= minimum)
			scale = minimum
		elsif (scale <= 0 && scale >= -minimum)
			scale = -minimum
		elsif !@use_ip && !no_rounding
			roundor = scale.round
			scale = roundor if (scale - roundor).abs < tolerance
			[0.5, 0.75, 0.25].each do |s|
				if (scale - s).abs < tolerance
					scale = (scale > 0) ? s : -s
					break
				end	
			end
		end	
		lscales.push scale
	end	
	@scales = lscales
	
	#Recomputing the target, simulating some inference
	for i in 0..2
		vtar[i] = vori[i] * @scales[i]
	end	
	@pt_target = @tr_box * Geom::Point3d.new(vtar[0], vtar[1], vtar[2])
	
	return true
end

#Scaling the box and the selection
def scale_the_box(view)
	#No need to do anything
	return if @mode_pads && !@highlight
	
	#Compute the Scaling transformation and total transformation (for scaling the box)
	compute_transformation
	
	#Computing the transformation by invoking the speific tool method
	tool__scale_the_box view
	
	compute_current_handles(view)
	@sel_handles = [@handles[@num_handles[0]], @handles[@num_handles[1]]] if @num_handles
	@bbox.each { |pt| @bounds.add pt }
	
	#Transforming the selected entities, unless there is no live deformation
	deform_the_entities view, (@dragging && !@livedeform)
	
	#Storing the previous transformation
	@tr_previous = @tr_new.inverse
	
end

#Compute the transformation resulting from scaling and changing to the box axes
def compute_transformation
	tool__compute_transformation
end

#Perform the deformation of the model selection
def deform_the_entities(view, unless_cond)
	return if unless_cond && @dragging

	abort_operation
	start_operation

	compute_transformation unless @tr_new

	#Deforming the entities by invoking the tool specific method
	tool__deform_entities
end

#Check if 2 screen points (in 2D) are close
def close2d_in_pixels?(x1, y1, x2, y2)
	(x1 - x2).abs <= @close_in_pixel && (y1 - y2).abs <= @close_in_pixel
end

#Switch between FredoScale and ScaleToTarget mode
def onLButtonDoubleClick(flags, x, y, view)
	return unless @sel_handles
	return if @post_validate
	pt2d = view.screen_coords @sel_handles[0].pt3d
	return unless close2d_in_pixels?(pt2d.x, pt2d.y, x, y)
	switch_free_target view unless @protractor
end

#Toggle between Free mode and Target mode
def switch_free_target(view)
	@dragging = false
	@mode_target = !@mode_target
	@hparam["Mode_Target"] = @mode_target
	@title_tool = F6_FredoScale.compute_title_tool @hparam
	if (@mode_target)
		activate_mode_target	
	else
		onSetCursor
		show_info
		view.invalidate
	end	
end

#Switch back to Scale tool, when Double click in the Target mode
def return_from_target
	return if @post_validate || @protractor
	switch_free_target @view
	self._select
end

#Switch to Protractor mode
#Activate the Target Mode, by swicthing control to the PickAngle tool
def activate_mode_protractor(view)
	@dragging = true
		
	#Creating and Launching the PickAngle Tool
	lv = compute_protractor_at_handle
	return unless lv
	@pickangletool = PickAngleTool.new @hparam, lv[0], lv[1], lv[2]
	@running_protractor = true
	@pickangletool.refresh_hsh_entityID
	@pickangletool._select	
end

#Execute from Protractor mode
def execute_from_protractor(origin, normal, basedir, angle)
	rotate_from_protractor @view, origin, normal, basedir, angle
	@angle_prev = angle
	deform_the_entities @model.active_view, @livedeform
	@post_validate = true if @mode_postvalidate
	self._select
end

#Activate the Target Mode, by swicthing control to the PickLine tool
def activate_mode_target
	return if @post_validate
	
	#Validate the current scaling if any
	validate_scaling true
	
	#Computing the imposed directions (vector or dir) depending on the degrees of freedom
	vec_line = normal_plane = nil
	vec_line = @sel_handles[1].pt3d.vector_to @sel_handles[0].pt3d if @dof == 1
	normal_plane = @normaldof2 if @dof == 2
	
	#Creating and Launching the PickLine Tool
	@picklinetool = PickLineTool.new @hparam
	@picklinetool.set_custom_axes @box_axes, T6[:TIP_ScalingBox]
	@running_target = true
	@dragging = true
	@picklinetool.refresh_hsh_entityID
	@picklinetool._select	
end

#Left Mouse Button Down pressed
def onLButtonDown(flags, x, y, view)
	@button_down = true
	@x_down = x
	@y_down = y
	execute_by_mode view
end

#Left Mouse Button released
def onLButtonUp(flags, x, y, view)
	@button_down = false
	if @sel_handles && @mode_target
		activate_mode_target
	elsif @dragging && ((x - @x_down).abs > @close_in_pixel || (y - @y_down).abs > @close_in_pixel)
		register_scaling
	end	
	view.invalidate
end

#start a new session
def new_session
	tool__new_session @new_orientation
	@new_orientation = false
end

#Start the proper action mode when button is clicked (or Enter is pressed)
def execute_by_mode(view)
	if @dragging
		register_scaling
	elsif @mode_divider && @within_divider	&& @within_divider > 0
		switch_to_pickdivider
	elsif @pk_normal || @pk_vecdir
		change_orientation
	elsif @sel_handles
		unless @num_handle_prev && @num_handles && 
			   @num_handle_prev == @num_handles[0] && @from_center_prev == @from_center
			validate_scaling unless @post_validate
			new_session
		end	
		if @new_orientation
			new_session
		end	
		@num_handle_prev = @num_handles[0] 
		@from_center_prev = @from_center
		@dragging = true unless @mode_target
		if @protractor
			activate_mode_protractor view
		end	
	elsif @ip.vertex == nil && @ip.edge == nil && @ip.face == nil 
		return exit_tool
	elsif @post_validate
		validate_scaling true
	end	
	view.invalidate
	show_info
end

#Change the orientation of the box
def change_orientation
	if @pk_vecdir == @NUL_AXIS
		@pk_vecdir0 = nil
		@pk_normal0 = nil	
	else
		@pk_vecdir0 = @pk_vecdir
		@pk_normal0 = @pk_normal
	end
	@post_validate = false
	if @operation || @pk_vecdir == @NUL_AXIS
		commit_operation
		activate
	else
		@scalebox.recompute_box @pk_normal0, @pk_vecdir0
		reset_bbox
	end
end

#Exit the FredoScale Tool - Validation of operation is done by method #deactivate
def exit_tool
	@model.select_tool nil
end

#Register the scaling handles for future validation if they are changed
def register_scaling
	@dragging = false
	
	#Keeping record of the active handles
	if @num_handles
		@num_handle_prev = @num_handles[0]
		@from_center_prev = @from_center
	end	
	
	#Proceeding with the visual update, when no interactive deformation	
	@selection = @model.selection
	deform_the_entities @model.active_view, true unless @livedeform
	
	#Flagging for post validation
	@post_validate = true if @mode_postvalidate
end

#Compute the new box after validation
def compute_box_after
	tool__compute_box_after
end

#Validate the scaling, and swap the current box to become the original box
def validate_scaling(force=false)
	@selection = @model.selection
	return if force == false && @num_handle_prev && @num_handles &&
			  (@num_handle_prev == @num_handles[0] && @from_center_prev == @from_center)
	commit_operation
	@lst_wireframe = compute_wireframe if @tr_new
	@tr_previous = @tr_identity
	@angle_prev = 0
	#@scales = [1.0, 1.0, 1.0] if force	#####Added for 2.0c
	@bbox0 = compute_box_after if @tr_box && @scales
	@tr_new = nil
	@handles0 = compute_handles @bbox0
	@sel_handles0 = [@handles0[@num_handles[0]], @handles0[@num_handles[1]]] if @num_handles
	if @post_validate
		activate
	end	
end

#Compute the new position of the box when dragging the handle
def do_dragging(view, x, y)
	compute_target_point view, x, y
	compute_box_transformation
	compute_scale_factors
	scale_the_box view
end

#Compute the Transformations to be used for scaling the selection from its original coordinates	
def compute_box_transformation
	if @from_center
		hpivot = (@mode_pads) ? @handles0[@highlight.ihcenter] : @hcenter
	else
		hpivot = @sel_handles0[1]
	end	
	@tr_box = Geom::Transformation.axes hpivot.pt3d, @box_axes0[0], @box_axes0[1], @box_axes0[2]
	@tr_box_inv = @tr_box.inverse
end

#Store the rotation parameter
def set_rotate_param(origin, normal, basedir, angle)
	@rotate_param.origin = origin
	@rotate_param.normal = normal
	@rotate_param.basedir = basedir
	@rotate_param.angle = angle
	@rotate_param
end

#Handle rotation called from the Protractor tool	
def rotate_from_protractor(view, origin, normal, basedir, angle)
	#Synchronize draw and move
	return if @moving
	@moving = true

	return unless @sel_handles
	
	#Setting the rotation parameters
	set_rotate_param origin, normal, basedir, angle + @angle_prev
	
	#executing the scaling
	compute_box_transformation
	scale_the_box view
	true
end

#Handle scaling based on origin and target points in mode Scale To Target	
def reach_target(view, pt_origin, pt_target)	

	#Case where there is no move
	vec = pt_origin.vector_to pt_target
	unless vec.valid?
		@scales = [1.0, 1.0, 1.0]
		compute_box_transformation
		scale_the_box view
		return true
	end
	
	#Computing the actual deformation
	case @dof
	when 1
		vecdir = @sel_handles[1].pt3d.vector_to @sel_handles[0].pt3d
		pt_origin = pt_origin.project_to_line [@pt_origin, vecdir]
		pt_target = pt_target.project_to_line [@pt_origin, vecdir]
	when 2
		pt_origin = pt_origin.project_to_plane [@pt_origin, @normaldof2]
		pt_target = pt_target.project_to_plane [@pt_origin, @normaldof2]
	end
	@pt_origin = pt_origin.clone
	@pt_target = pt_target.clone
	@deltavec = @pt_origin.vector_to @pt_target

	#executing the scaling
	compute_box_transformation
	return false unless compute_scale_factors
	scale_the_box view
	true
end

#Switch to the Pickdivider tool
def switch_to_pickdivider
	@pickdivider = PickDividerTool.new @hparam, @pt3d_divider, @box_axes, @iaxes[0]
	@running_divider = true
	@pickdivider._select	
end

#Compute the current pivot center for the active operation
def compute_pivot
	@hpivot = (@from_center) ? @hcenter : @sel_handles0[1]
end

#Test if the Origin point for a Scale to Target is valid	
def test_origin(pt_origin)
	hpivot = (@from_center) ? @hcenter : @sel_handles0[1]
	handle = @sel_handles[0]
	ptpivot = hpivot.pt3d
	pthandle = handle.pt3d
	dh = pthandle.distance ptpivot
	
	#Checking that the point is on the right side of the box
	@haxes.each do |vec|
		next unless vec
		ptproj = pt_origin.project_to_line [ptpivot, vec]
		d = ptpivot.distance ptproj
		if ((d / dh) < LIMIT_SmallScale) || (!@from_center && (vec % ptpivot.vector_to(ptproj) <= 0))
			return false
		end	
	end
	true
end	

#Method called when the Origin / target selection is done by the PickLine Tool	
def execute_from_target
	deform_the_entities @model.active_view, @livedeform
	@post_validate = true if @mode_postvalidate
	validate_scaling true
	@dragging = false
	@model.select_tool self
	@sel_handles = nil
end

#Mouse Move methods
def onMouseMove_zero
	if @running_target
		@picklinetool.onMouseMove_zero
	elsif @running_protractor
		@pickangletool.onMouseMove_zero
	else
		onMouseMove 0, @x, @y, @view if @x
	end	
end
	
def onMouseMove(flags, x, y, view)
	#Synchronize draw and move
	return if @moving
	@moving = true
	
	compute_current_handles view unless @handles
	@x = x
	@y = y
	@pk_edge = @pk_face = nil
	@pk_vecdir = @pk_normal = nil
		
	#Dragging mode	
	if @dragging
		do_dragging view, x, y
	
	#check if Mouse is on a handle
	elsif mouse_within_handle(view, x, y)
		
	#Post validate in postvalidate mode
	elsif @mode_postvalidate && @post_validate
		@ip.pick view, x, y
		
	#check if Mouse is on a divider
	elsif @mode_divider && @lpt_divider && mouse_within_divider(view, x, y)
		
	#Checking if an edge or face is selected
	else
		@ip.pick view, x, y
		if @dim > 1 && @ip.edge
			@pk_edge = @ip.edge
			pt1 = @ip.transformation * @pk_edge.start.position
			pt2 = @ip.transformation * @pk_edge.end.position
			@pk_vecdir = pt1.vector_to pt2
			@pk_edge = @pk_vecdir = nil if already_in_box?(@pk_vecdir)
			@lpt_edge = [pt1, pt2]
		end
		if @dim > 2 && @ip.face
			@pk_face = @ip.face
			@pk_normal = G6::transform_vector @pk_face.normal, @ip.transformation
			@pk_normal = @pk_face = nil if already_in_box?(@pk_normal) && @pk_edge == nil
		end	
	end	
	compute_tooltip view
	onSetCursor
	view.invalidate
	show_info
end	

#Check if a vector is colinear with one of the axes of the scaling box
def already_in_box?(vec)
	return false unless vec.valid?
	@box_axes.each { |v| return true if v.valid? && v.parallel?(vec) }
	false
end

#Manage the display of the status bar and VCB
def show_info
	#VCB display
	if @mode_vcbangle
		label = @label_angle
		svalue = @text_angle if @text_angle
	else
		label = axes_to_nice_text
		svalue = scales_to_nice_text
	end	
	
	#status message
	if @dragging
		stext = @title_tool + ': ' + @title_drag
	else	
		stext = @title_tool + ': ' + @title_grip
	end	
	sdim = dims_to_nice_text
	stext += " - " + sdim if sdim != ""

	Sketchup::set_status_text stext
	Sketchup::set_status_text label, SB_VCB_LABEL
	Sketchup::set_status_text svalue, SB_VCB_VALUE	
end

#Compute a nice text for showing the axes involved in scaling
def axes_to_nice_text
	return ((@from_center) ? 'o-> ' : "") + @label_axes if @label_axes
	""	
end

#Compute a nice text for the box dimensions
def dims_to_nice_text
	return "" unless @iaxes
	ldim = current_box_dimensions()
	laxes = []
	@iaxes.each { |i| laxes.push [i, ldim[i]] if i }
	laxes.sort! { |a, b| a[0] <=> b[0] }
	ls = laxes.collect { |a| a[1] }
	txt_ls = ls.collect { |s| s.to_l }
	text = '[' + txt_ls.join("; ") + ']'
	text = text.gsub(/\~\s/, "~")
	return text
end

#Compute a nice text for the scaling factors
def scales_to_nice_text
	return "" unless @scales
	scales = (!@dragging && @num_handles && @num_handle_prev != @num_handles[0]) ? [1, 1, 1] : @scales
	laxes = []
	@iaxes.each { |i| laxes.push [i, scales[i]] if i }
	laxes.sort! { |a, b| a[0] <=> b[0] }
	ls = laxes.collect { |a| a[1] }
	ls = [ls[0]] unless @freedof
	txt_ls = ls.collect { |s| sprintf("%4.2f", s) if s }
	return txt_ls.join("  ")
end

#Compute the tooltip to be displayed in the view
def compute_tooltip(view)
	if @pk_edge
		tooltip = T6[:TIP_Edge]
	elsif @pk_face
		tooltip = T6[:TIP_Face]
	elsif @sel_handles && !@dragging && !@mode_target
		tooltip = @label_axes + "\n"
		if @from_center
			if @freedof && @dof > 1
				tooltip += T6[:TIP_FromCenter]
			else	
				tooltip += T6[:TIP_UFromCenter]
			end	
		else
			if @freedof && @dof > 1
				tooltip += T6[:TIP_Opposite]
			else	
				tooltip += T6[:TIP_UOpposite]
			end	
		end	
	elsif @dragging && @scales
		tooltip = (@use_ip) ? @ip.tooltip + "\n" : "" 
		tooltip += axes_to_nice_text + " -> " + scales_to_nice_text + "\n" + dims_to_nice_text
	elsif @dragging == false && @ip.vertex == nil && @ip.edge == nil && @ip.face == nil 
		tooltip = T6[:TIP_ClickExit]
	elsif @post_validate
		tooltip = T6[:TIP_PostValidate]
	else
		tooltip = ""
	end	
	view.tooltip = tooltip 
end

#Execute the scaling of the box and selection in one shot
def execute_scaling
	view = @model.active_view
	validate_scaling
	compute_box_transformation
	register_scaling
	scale_the_box view
	view.invalidate
	show_info
end

#Standard method to accept text in the VCB
def onUserText(text, view)     
	Traductor::ReturnUp.set_on
	if @scales
		if @mode_vcbangle && @protractor
			status = parseVCB_as_rotation(text, view)
		elsif @mode_vcbangle
			status = parseVCB_as_angle(text, view)
		else
			status = parseVCB_as_scale(text, view)
			@deltavec = nil
		end	
		unless @num_handle_prev && @num_handles && 
			   @num_handle_prev == @num_handles[0] && @from_center_prev == @from_center
			new_session
		end	
		return execute_scaling if status
	end	
	UI.beep
end

#Parse the VCB for scales and dimensions
def parseVCB_as_rotation(text, view)
	dangle = Traductor.string_to_angle_degree text
	return false unless dangle
	angle = dangle.degrees
	@rotate_param.angle = angle
	true
end

#Parse the VCB for scales and dimensions
def parseVCB_as_angle(text, view)
	#Replacing commas by decimal points
	dangle = Traductor.string_to_angle_degree text
	return false unless dangle
	dangle = 85 if dangle > 85
	dangle = -85 if dangle < -85
	scale = scale_from_planarshear_angle dangle.degrees
	@scales = [1.0, 1.0, 1.0]
	@scales[@iaxes[0]] = scale
	true
end

#Parse the VCB for scales and dimensions
def parseVCB_as_scale(text, view)

	#Replacing commas by decimal points
	text = text.gsub(/,\d/) { |s| '.' + s[1..1] }
	
	#Splitting the text based on space or semi-column
	lstx = text.split(";")
	lscale = []
	lstx.each { |t| lscale += t.split(" ") }
	
	#Relevant axes
	haxes = {}
	@iaxes.each { |i| haxes[i] = true if i }
	
	#Parsing the string
	lval = []
	lscale.each do |s|
		indx = -1
		if s.length > 1
			if s =~ /r\Z/i
				indx = 0 if haxes[0]
				s = $`
			elsif s =~ /g\Z/i
				indx = 1 if haxes[1]
				s = $`
			elsif s =~ /v\Z/i
				indx = 2 if haxes[2]
				s = $`
			end	
		end
		val = (s == "") ? 1.0 : Traductor.string_to_float_formula(s)	
		return false unless val
		lval.push [val, indx]
	end
	
	#Finding the right scales
	scales = []
	lval.each do |lv|
		val = lv[0]
		indx = lv[1]
		if (indx >= 0)
			scales[indx] = val if haxes[indx]
		elsif @freedof
			for i in 0..2
				unless scales[i] || !haxes[i]
					scales[i] = val
					break
				end	
			end			
		else
			for i in 0..2
				scales[i] = val unless scales[i] || !haxes[i]
			end
		end		
	end
	
	#Finalizing
	for i in 0..2
		scales[i] = 1.0 unless scales[i]
	end	
	@scales = scales
	return true
end


#------------------------------------------------------------------------------
# Methods specific to the transformation type
#------------------------------------------------------------------------------

#Compute the scale corresponding to a given angle (given by its tangent)
def scale_from_planarshear_angle(angle)
	return unless @highlight
	tgt = Math.tan angle
	ih = @highlight.ih
	oih = @highlight.oih
	iopp = @handles[ih].opp
	h = @handles0[oih].pt3d.distance @handles0[iopp].pt3d
	l = @handles0[oih].pt3d.distance(@handles0[ih].pt3d)
	scale = 1.0 + tgt * h / l 
end

def planarshear_angle_from_scale(scale)
	return unless @highlight
	ih = @highlight.ih
	oih = @highlight.oih
	iopp = @handles[ih].opp
	h = @handles0[oih].pt3d.distance @handles0[iopp].pt3d
	l = @handles0[oih].pt3d.distance(@handles0[ih].pt3d)
	tgt = l * (scale - 1.0) / h
	tgt = 0 if tgt.abs < 0.0001
	tgt	
end

#Compute the parameters of the protractor at a selected handle. Return [origin, normal, basedir]
def compute_protractor_at_handle
	return nil unless @sel_handles
	cur_handle = @sel_handles[0]
	opp_handle = @handles[cur_handle.opp]
	type = cur_handle.type
	origin = cur_handle.pt3d
	
	#Dimension 1
	if @dim == 1
		vec1 = @handles[0].pt3d.vector_to @handles[1].pt3d
		axes = vec1.axes
		if type == 'C' 
			normal = axes[1]
			basedir = axes[0]
		elsif type == 'B'
			normal = axes[0]
			basedir = axes[1]
		end	
		
	#dimension 2
	elsif @dim == 2
		vec1 = @handles[0].pt3d.vector_to @handles[1].pt3d
		vec2 = @handles[1].pt3d.vector_to @handles[2].pt3d
		normal = vec1 * vec2
		if type == 'F' || type == 'B' || type == 'C'
			basedir = nil
		else
			normal = opp_handle.pt3d.vector_to(origin) * normal
			basedir = nil
		end	
	
	#dimension 3
	else
		if type == 'F'
			normal = opp_handle.pt3d.vector_to origin
			basedir = nil
		else type == 'M'
			normal = @normaldof2
			basedir = nil
		end	
	end
	
	#return  value
	return nil unless normal
	@rotate_param.origin = origin
	@rotate_param.normal = normal
	@rotate_param.basedir = basedir
	@rotate_param.angle = 0
	[origin, normal, basedir]
end

#Collect the list of edge vertices with absolute coordinates
def compute_wireframe
	tool__compute_wireframe
end

end	#class ScaleTool

end	#End Module F6_FredoScale
