=begin
#-------------------------------------------------------------------------------------------------------------------------------------------------
#*************************************************************************************************
# Designed July 2013 by Fredo6

# 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			:   JointPushPullTool.rb
# Original Date	:   15 Jul 2013
# Description	:   JointPushPull Interactive Tool
#-------------------------------------------------------------------------------------------------------------------------------------------------
#*************************************************************************************************
=end

module F6_JointPushPull

T6[:SBTEXT_Selection] = "Click to select / unselect faces - Click and move to start dragging - Long click for free dragging - Double-click to repeat push pull"
T6[:SBTEXT_SelectionGeom] = "Modify parameters applicable to generated geometry or Select faces or click in empty space to exit"
T6[:SBTEXT_Dragging] = "Drag face to desire offset"
T6[:SBTEXT_Preview] = "Preview mode - Change of parameters will be reflected"
T6[:SBTEXT_Geometry] = "Generating geometry"

T6[:MSG_FacesSelected] = "Faces selected: %1"
T6[:MSG_GroupsSelected] = "Groups: %1"
T6[:MSG_ComponentsSelected] = "Components: %1"

T6[:TXT_BordersContour] = "CONTOUR"
T6[:TXT_BordersGrid] = "GRID"
T6[:TXT_BordersNone] = "NONE"
T6[:TXT_BordersOption] = "Borders"
T6[:TXT_GenerationGroup] = "Generation in GROUP"
T6[:TXT_InferenceOn] = "Inference ON"
T6[:TXT_InferenceOff] = "Inference OFF"
T6[:TXT_Thickening] = "Thickening"
T6[:TXT_PushPulling] = "Push-Pull"

T6[:MSG_ProcessingTime] = "Processing time = %1s"

T6[:ERR_PrepareCalculation] = "Error in Analysis of model"
T6[:ERR_Geometry] = "Error in generation of the geometry"

T6[:TIP_RollbackInSelection] = "Undo Selection"
T6[:TIP_RollbackToSelection] = "Back to Selection"
T6[:TIP_RollbackToPreview] = "Undo Geometry and Back to Preview"

T6[:TIP_InError1] = "Face Selection cannot be push-pulled"
T6[:TIP_InError2] = "Please Modify the Selection"

T6[:TIP_DesiredOffset] = "Drag mouse to desired Offset (or type offset in VCB)"

#--------------------------------------------------------------------------------------------------------------
# Top Calling functions: create the classes and launch the tools
#--------------------------------------------------------------------------------------------------------------			 				   

#Actual launching of menus	
def F6_JointPushPull.action__mapping(action_code, *args)
	Sketchup.active_model.select_tool JointPushPullTool.new(action_code, *args)
end

#=============================================================================================
#=============================================================================================
# Class JointPushPullTool: main class for the Interactive tool
#=============================================================================================
#=============================================================================================

class JointPushPullTool < Traductor::PaletteSuperTool

#INIT: Class instance initialization
def initialize(jpp_mode, *args)
	@jpp_mode = jpp_mode

	#Loading default parameters
	proc = self.method "notify_from_defparam"
	MYDEFPARAM.add_notify_proc(proc, true)
	notify_from_defparam nil, nil
	
	#Loading parameters
	options_load

	#Basic initialization
	@model = Sketchup.active_model
	@rendering_options = Sketchup.active_model.rendering_options
	@tw = Sketchup.create_texture_writer
	@tr_id = Geom::Transformation.new
	@title = F6_JointPushPull.get_title(jpp_mode)
	jpp_mode_init
	@pixel_dragging = 5
	@inference_on = true
	@angle_round_min = 0
	@angle_round_max = 60
	@segment_round_min = 2
	@segment_round_max = 16
	@golden_circular_pts = []
	@shape_grid = G6::ShapeGrid.new 4
	
	#Parsing the arguments
	args.each { |arg| arg.each { |key, value|  parse_args(key, value) } if arg.class == Hash }

	#Static initialization
	init_cursors
	init_colors
	init_messages

	#Initializing the Key manager
	@keyman = Traductor::KeyManager.new() { |event, key, view| key_manage(event, key, view) }

	#Initializing the Zoom Manager
	@zoom_void = G6::ZoomVoid.new
	
	#Initializing the Face Picker
	init_face_picker

	#Initializing the Direction Manager
	init_direction_manager
	
	#Creating the palette manager and texts
	init_palette
	
	#Initialize the VCB
	init_VCB
end

#INIT: Parse the arguments of the initialize method
def parse_args(key, value)
	skey = key.to_s
	case skey
	when /from/i
		@invoked_from = value
	end	
end	

#INIT: Notification when default parameters are changed
def notify_from_defparam(key, val)
	@param_no_tooltip = MYDEFPARAM[:JPP_PARAM_NoTooltip]
	@param_preview_on_component = MYDEFPARAM[:JPP_PARAM_Preview_on_component]
	@param_random_extended = MYDEFPARAM[:JPP_PARAM_RandomExtended]
	@param_auto_geom = MYDEFPARAM[:JPP_PARAM_AutoGeom]
	@param_color_adjacent = MYDEFPARAM[:JPP_PARAM_ColorAdjacent]
	@param_preview_faces = MYDEFPARAM[:JPP_PARAM_PreviewFaces]
	@param_preview_borders = MYDEFPARAM[:JPP_PARAM_PreviewBorders]
	@param_auto_soften_borders = MYDEFPARAM[:JPP_PARAM_AutoSoftenBorders]
	
	@facepicker.set_no_tooltip(@param_no_tooltip) if @facepicker
end

#---------------------------------------------------------------------------------------------
# OPTIONS: Option Management and storage
#---------------------------------------------------------------------------------------------
	
#OPTIONS: Load ALL options from registry
def options_load
	@lst_jpp_modes = [:joint, :round, :normal, :vector, :extrude, :follow]
	@hsh_options_global_default = {}
	
	#Defaults for local options
	hloc = @hsh_options_local_default = {}
	
	hcommon = { :offset => 0, :gen_group => false, :borders => :contour, :planar_code => :no, :planar_dir_custom => nil, 
	            :planar_local => true, :thickening => false, :coquad => false, :last_vector => nil,
				:random_on => false, :random_seed => 2345, :randf_min => 0.5, :randf_max => 2, :radial_scaling => nil }
	
	hloc[:joint] = { :thickening => true, :finishing => :thicken, :neighbour_influence => false }
	hloc[:round] = { :thickening => true, :finishing => :thicken, :angle_round => 20, :segment_round => 6 }
	hloc[:normal] = { :finishing => :push, :borders => :grid, :coquad => true, :radial_scaling => 1.0 }
	hloc[:extrude] = { :finishing => :push, :extrude_flat => false, :radial_scaling => 1.0 }
	hloc[:vector] = { :finishing => :push, :vector_projected => false }
	hloc[:follow] = { :finishing => :push }
	
	hloc.each { |symb, hsh| hcommon.each { |key, val| hsh[key] = val unless hsh.has_key?(key) } }
	
	#Loading global options
	options_load_global
	
	#Loading local options
	options_load_local
end
	
#OPTIONS: Load GLOBAL options from registry
def options_load_global
	@hsh_options_global_default = {}
	
	#Global parameters for JointPushPull
	sparam = Sketchup.read_default "F6_JointPushPull", "Options"
	hsh = {}
	if sparam
		hsh = eval(sparam) rescue {}
	end
	
	#Transferring values
	@hsh_options_global = {}
	@hsh_options_global_default.each { |key, val| @hsh_options_global[key] = val }
	hsh.each { |key, val| @hsh_options_global[key] = val unless hsh[key] == nil }
end

#OPTIONS: Load LOCAL options from registry
def options_load_local
	@hsh_options_local = {} unless @hsh_options_local
	hloc_def = @hsh_options_local_default[@jpp_mode]
	
	sparam = Sketchup.read_default "F6_JointPushPull", "Options_#{@jpp_mode}"
	hsh = {}
	if sparam
		hsh = eval(sparam) rescue {}
	end
	hloc_def.each { |key, val| @hsh_options_local[key] = val }
	hsh.each { |key, val| @hsh_options_local[key] = hsh[key] if hloc_def.has_key?(key) && hsh[key] != nil}
end

#OPTIONS: Save options to registry
def options_save
	#Getting the facepicker parameters
	hsh = @facepicker.get_hparams
	@hsh_options_global[:facepicker_option_face_selection] = hsh[:option_face_selection]
	
	#Saving global options
	sparam = @hsh_options_global.inspect.gsub('"', "'")
	Sketchup.write_default "F6_JointPushPull", "Options", sparam	
	
	#Saving local options
	#@hsh_options_local[:offset] = @last_offset
	@hsh_options_local[:offset] = @offset
	if @jpp_mode == :round
		@hsh_options_local[:angle_round] = @angle_round
		@hsh_options_local[:segment_round] = @segment_round
	end	
	planar_code = @dirman.get_direction_code
	v = @dirman.get_vector_custom
	if v
		@hsh_options_local[:planar_dir_custom] = v.to_a
	else
		@hsh_options_local[:planar_dir_custom] = nil
		planar_code = :no if planar_code == :c
	end
	@hsh_options_local[:last_vector] = (@jpp_mode == :vector && @last_vector_cur) ? @last_vector_cur.to_a : nil
	@hsh_options_local[:planar_code] = planar_code
	@hsh_options_local[:planar_local] = @dirman.get_scope_local
	
	sparam = @hsh_options_local.inspect.gsub('"', "'")
	Sketchup.write_default "F6_JointPushPull", "Options_#{@jpp_mode}", sparam
end
	
#OPTIONS: Get an option
def option_get(symb)
	hloc = @hsh_options_local
	return hloc[symb] if hloc.has_key?(symb)
	@hsh_options_local_default[@jpp_mode][symb]
end

#OPTIONS: Set an option
def option_transfer(symb, val)
	@hsh_options_local[symb] = val
end

#OPTIONS: Set an option
def option_set(symb, val, change=nil)
	return if @hsh_options_local[symb] == val
	@hsh_options_local[symb] = val
	option_after(change)
	@palette_message = nil
	compute_palette_message
	val
end
	
#OPTIONS: Set an option
def option_toggle(symb, change=nil)
	val = @hsh_options_local[symb]
	@hsh_options_local[symb] = !val
	option_after(change)
	@palette_message = nil
	compute_palette_message
	val
end
	
#OPTIONS: post-treatment after options are changed	
def option_after(change)
	keep_dragging = false
	if change
		change = [change] unless change.class == Array
		change.each do |param|
			case param
			when :vertices
				@prepare_vertices_done = false
			when :keep_dragging
				keep_dragging = true
			end
		end	
	end	
	@initial_face_cur = nil
	modify_execute keep_dragging
	if change && change.include?(:shield) && @lst_blocks && (@mode == :dragging || @mode == :preview)
		shield_select_candidates
		hiding_manage_faces+
		wireframe_compute
	end
end
	
#---------------------------------------------------------------------------------------------
# INIT: Static initializations
#---------------------------------------------------------------------------------------------
					
#INIT: Static properties depending on JPP Mode	
def jpp_mode_init
	@jpp_prop_random = @param_random_extended
	
	case @jpp_mode
	when :joint
		@jpp_prop_jointive = true
		@jpp_prop_flat = false
		@jpp_prop_vector = false
		@jpp_prop_radial_scaling = false
		@jpp_letter = "J"
	when :round
		@jpp_prop_jointive = false
		@jpp_prop_flat = false
		@jpp_prop_vector = false
		@jpp_prop_random = false
		@jpp_prop_radial_scaling = false
		@jpp_letter = "R"
	when :vector
		@jpp_prop_jointive = true
		@jpp_prop_flat = true
		@jpp_prop_vector = true
		@jpp_prop_radial_scaling = false
		@jpp_letter = "V"
	when :extrude
		@jpp_prop_jointive = true	
		@jpp_prop_flat = true
		@jpp_prop_vector = true
		@jpp_prop_random = false
		@jpp_prop_radial_scaling = true
		@jpp_letter = "X"
	when :follow
		@jpp_prop_jointive = true	
		@jpp_prop_flat = false
		@jpp_prop_vector = false
		@jpp_prop_radial_scaling = false
		@jpp_letter = "F"
	when :normal
		@jpp_prop_jointive = false
		@jpp_prop_flat = true
		@jpp_prop_vector = false
		@jpp_prop_random = true
		@jpp_prop_radial_scaling = true
		@jpp_letter = "N"
	end
	
	#Setting the offset
	@offset = @hsh_options_local[:offset] unless @offset
	@offset = 0 unless @offset
	@last_offset = @offset
	@offset_text = (@offset) ? Sketchup.format_length(@offset) : ""	

	#Setting the round angle
	@segment_round = @hsh_options_local[:segment_round] unless @segment_round
	@angle_round = @hsh_options_local[:angle_round] unless @angle_round
	@angle_round = 20 unless @angle_round
	
	@option_thickening = option_get :thickening
	
	#Setting the default cursor
	@id_cursor_dragging = MYPLUGIN.create_cursor "JPP_icon_#{@jpp_letter}_24", 10, 1
	@id_cursor_dragging_plus = MYPLUGIN.create_cursor "JPP_icon_#{@jpp_letter}_plus_24", 10, 1
	@id_cursor_dragging_inference = @id_cursor_dragging
	@id_cursor_picking = @id_cursor_dragging
	@id_cursor_picking_plus = @id_cursor_dragging_plus
	@id_cursor_picking_plus_inference = @id_cursor_dragging_plus
	
	#Setting some intrinsic properties of the mode
	if @jpp_prop_jointive && !@jpp_prop_random 
		@proc_key_pseudo = proc { |vx_id, face_id| vx_id }
	else
		@proc_key_pseudo = proc { |vx_id, face_id| "#{vx_id}-#{face_id}" }
	end	
end

#INIT: Initialize texts and messages
def init_messages
	@mnu_exit = T6[:T_BUTTON_Exit]	
	@mnu_abort = T6[:T_STR_AbortTool]
	@msg_offset = T6[:T_TXT_Offset]
	
	@tip_rollback_in_selection = T6[:TIP_RollbackInSelection]
	@tip_rollback_to_selection = T6[:TIP_RollbackToSelection]
	@tip_rollback_to_preview = T6[:TIP_RollbackToPreview]
	
	@tip_desired_offset = T6[:TIP_DesiredOffset] + "\n"
	@tip_release_preview = @tip_desired_offset + T6[:TIP_ReleaseToPreview]
	@tip_click_release_preview = @tip_desired_offset + T6[:TIP_ClickReleaseToPreview]
	@tip_release_geom = @tip_desired_offset + T6[:TIP_ReleaseToGeom]
	@tip_click_release_geom = @tip_desired_offset + T6[:TIP_ClickReleaseToGeom]
	@tip_generate_geometry = T6[:TIP_ValidateToGeometry]
	@tip_click_exit = T6[:TIP_ClickExit]
	@tip_in_error = T6[:TIP_InError1] + "\n" + T6[:TIP_InError2]
	
	@sbtext_selection = T6[:SBTEXT_Selection]
	@sbtext_selection_geom = T6[:SBTEXT_SelectionGeom]
	@sbtext_dragging = T6[:SBTEXT_Dragging]
	@sbtext_preview = T6[:SBTEXT_Preview]
	@sbtext_geometry = T6[:SBTEXT_Geometry]
	
	@txt_border_options = T6[:TXT_BordersOption]
	@txt_gen_group = T6[:TXT_GenerationGroup]
	@txt_inference_on = T6[:TXT_InferenceOn]
	@txt_inference_off = T6[:TXT_InferenceOff]
	@txt_thickening = T6[:TXT_Thickening]
	@txt_push_pulling = T6[:TXT_PushPulling]

	@hmsg_borders = { :contour => T6[:TXT_BordersContour], :grid => T6[:TXT_BordersGrid], :none => T6[:TXT_BordersNone] }
end

#INIT: Initialize colors
def init_colors
	@color_message = 'khaki'
	@hsh_boxinfo_tip = { :bk_color => 'lightyellow', :fr_color => 'yellow' }
	@hsh_boxinfo_geom = { :bk_color => 'lightgreen', :fr_color => 'green' }					 
	@hsh_boxinfo_preview = { :bk_color => 'lightblue', :fr_color => 'blue' }					 
	@hsh_boxinfo_exit = { :bk_color => 'lightgreen', :fr_color => 'green' }					 
	@hsh_boxinfo_error = { :bk_color => 'orangered', :fr_color => 'black' }					 

	@pal_bk_color = 'lightblue'	
	@pal_hi_color = 'lightgreen'	
	
	@su_face_front_color = @rendering_options['FaceFrontColor']
	@su_face_back_color = @rendering_options['FaceBackColor']
end

#INIT: Initialize cursors
def init_cursors
	@id_cursor_arrow_exit = Traductor.create_cursor "Cursor_Arrow_Exit", 0, 0	
	@id_cursor_validate = Traductor.create_cursor "Cursor_Validate", 0, 0	
	@id_cursor_hourglass_green = Traductor.create_cursor "Cursor_hourGlass_Green", 16, 16		
	@id_cursor_hourglass_red = Traductor.create_cursor "Cursor_hourGlass_Red", 16, 16		
	@id_cursor = @id_cursor_picking_inference
end

#INIT: Initializating the Edge Selection Picker
def init_face_picker
	hsh = {}
	hsh[:notify_proc] = self.method 'notify_from_facepicker'
	hsh[:option_face_selection] = @hsh_options_global[:facepicker_option_face_selection]
	hsh[:title] = @title
	hsh[:text_drag] = T6[:TIP_DragToPushPull]
	hsh[:text_reselect] = T6[:TIP_DragReselect]
	hsh[:no_tooltip] = @param_no_tooltip
	hsh[:make_group_unique_proc] = self.method "make_group_unique_proc"
	@facepicker = Traductor::FacePicker.new hsh
end

def make_group_unique_proc(parent)
	@suops.abort_operation
	@suops.start_operation T6[:T_OPS_MakeGroupUnique]
	parent.make_unique
	@suops.commit_operation
	start_context_operation
end

#INIT: Initializating the Edge Selection Picker
def init_direction_manager
	hsh = {}
	hsh[:notify_proc] = self.method 'notify_from_dirman'
	hsh[:scope_local] = option_get :planar_local
	hsh[:vec_dir_custom] = option_get :planar_dir_custom
	hsh[:code] = option_get :planar_code
	hsh[:select_vector] = (@jpp_mode == :vector)
	@dirman = Traductor::DirectionManager.new hsh
	@planar_dir = @dirman.get_vector
	@planar_local = @dirman.get_scope_local
	compute_vector_lock
	compute_drawing_param_direction
	
	#Setting the default vector direction
	if @jpp_mode == :vector
		@last_vector_cur = nil
		if !@planar_dir
			lv = option_get(:last_vector)
			@last_vector_cur = Geom::Vector3d.new *lv if lv
			@vector_lock = @last_vector_cur
		end	
	end
end

#--------------------------------------------------
# Activation / Deactivation
#--------------------------------------------------

#ACTIVATION: Tool activation
def activate
	LibFredo6.register_ruby "JointPushPull"
	Traductor::Hilitor.stop_all_active
	@model = Sketchup.active_model
	@selection = @model.selection
	@entities = @model.active_entities
	@view = @model.active_view
		
	#Resetting the environment
	reset_context
	
	#Initiating the palette
	set_state_mode :selection

	#Initializing the Executor of Operation
	hsh = { :title => @title, :palette => @palette, :end_proc => self.method('geometry_terminate'),
            :no_commit => true }
	@suops = Traductor::SUOperation.new hsh
	start_context_operation

	#Handling the initial selection
	handle_initial_selection
	palette_set_infobox
		
	refresh_viewport
end

#ACTIVATION: Start the contextual operation
def start_context_operation
	@suops.start_operation
	@temp_layer = Traductor::TemporaryLayer.new unless @temp_layer
	@temp_layer.layer.visible = false
end

#ACTIVATION: Reset context variables and environment
def reset_context
	@button_down = false
	@ip_origin = Sketchup::InputPoint.new
	@ip_target = Sketchup::InputPoint.new
	@ip_cur = Sketchup::InputPoint.new
	@ph = @view.pick_helper
end

#ACTIVATION: Tool Deactivation
def deactivate(view)
	options_save
	
	#For normal termination, validate the geometry
	unless @aborting
		geom_gen = @geometry_generated
		geometry_commit
		@suops.abort_operation
	end

	#Restoring the selection unless geometry was generated
	if geom_gen && !@aborting
		@selection.clear
	else
		selection_at_exit
	end	
	@suops.abort_operation

	view.invalidate
end

#ACTIVATION: Manage the initial selection or the previous results
def handle_initial_selection
	@old_selection = @selection.to_a.clone
	lse = @selection.grep(Sketchup::Face) + @selection.grep(Sketchup::Group) + @selection.grep(Sketchup::ComponentInstance)
	@old_selection_ids = lse.collect { |e| e.entityID }
	return false if @selection.empty?
	
	@selection.clear
	@facepicker.selection_initial @old_selection
end
	
#ACTIVATION: Set / reset the selection at exit
def selection_at_exit
	@selection.clear
	return if @old_selection.empty?
	
	#Remapping the old selection
	hmaster_faceid = {}
	entities = @model.active_entities
	lse = entities.grep(Sketchup::Face) + entities.grep(Sketchup::Group) + entities.grep(Sketchup::ComponentInstance)
	lse.each { |e| hmaster_faceid[e.entityID] = e }
	
	#Restoring the selection
	sel = @old_selection_ids.collect { |id| hmaster_faceid[id] }
	sel = sel.find_all { |e| e && e.parent == @model }
	@selection.add sel unless sel.empty?
end
		
#ACTIVATION: Exit the tool
def exit_tool
	@aborting = false
	@model.select_tool nil
end

#--------------------------------------------------
# MENU: Contextual menu
#--------------------------------------------------

#MENU: Contextual menu
def getMenu(menu)
	cxmenu = Traductor::ContextMenu.new
		
	#Face Selection Menu
	if @mode == :selection
		@facepicker.contextual_menu_contribution(cxmenu)
	end	
	
	#Abort
	cxmenu.add_sepa
	cxmenu.add_item(@mnu_abort) { abort_tool }

	#Exit
	if @mode == :geometry
		cxmenu.add_sepa
		cxmenu.add_item(@mnu_exit) { exit_tool }
	end
	
	#Showing the menu
	cxmenu.show menu
	true
end

#--------------------------------------------------
# EXEC: State machine and Top Execution
#--------------------------------------------------

#EXEC: Set the current mode
def set_state_mode(mode)
	@mode = mode
	@palette.visible_family @mode
	case @mode
	when :selection
		@mode_status_text = (@geometry_generated) ? @sbtext_selection_geom : @sbtext_selection
	when :dragging
		@offset_prev = nil
		@mode_status_text = @sbtext_dragging
	when :preview
		@geometry_generated = false
		@mode_status_text = @sbtext_preview
	when :geometry
		deselection
		@mode_status_text = @sbtext_geometry
	end	
	palette_set_infobox
	compute_palette_message
	
	onSetCursor
	refresh_viewport
end

#EXEC: Notification from Face Picker
def notify_from_facepicker(action)
	@in_error = false
	case action
	when :selection
		geometry_commit if @geometry_generated
		set_state_mode :selection
		palette_set_infobox
	when :reselect
		dragging_start
	end	
end

#EXEC: Notification from Face Picker
def notify_from_dirman(vec_dir, scope_local)
	return if @planar_dir && @planar_dir == vec_dir && @planar_local == scope_local
	
	@planar_dir = vec_dir
	@planar_local = scope_local
	
	if @jpp_mode == :vector
		@vector_lock = @planar_dir
	end	
	modify_planar_direction
end

#ALGO: Prepare the key data for calculation
def selection_freeze_positions
	#Adding the current selection if just hovered
	@facepicker.selection_validate_if_add
	
	#Storing the current selection information
	@initial_face, @tr, @parent = @facepicker.selection_get_picked_face_info
	@picked_groupings = @facepicker.selection_get_groupings
end

#--------------------------------------------------
# DRAGGING: Manage the dragging in preview mode
#--------------------------------------------------

#DRAGGING: Start of dragging
def dragging_start
	set_state_mode :dragging
	wireframe_init
	algo_prepare_calculation
end

#DRAGGING: Stop of dragging
def dragging_stop
	go_preview_or_geom
end

#DRAGGING: Hanlde mouse movements while dragging
def dragging_move(view, x, y)
	
	@target_cur, @vector_cur, inference_ip = visual_compute_target_vector view, x, y
	
	#Checking the vector
	unless @target_cur
		UI.beep 
		@in_error = true
		set_state_mode :selection
		return
	end
	
	#Hiding faces depending on offset
	hiding_manage_faces if @offset != 0 && !@offset_prev
	
	#Testing occulting and inferences
	@last_vector_cur = @vector_cur
	dragging_offset_compute
	if inference_ip && !shield_occulting?(view, inference_ip)
		@target_cur = inference_ip.position.project_to_line [@origin_ini, @vector_cur]
		@ip_target_to_draw = :ip
		dragging_offset_compute
	end	
		
	view.tooltip = "  " + @ip_target.tooltip if @ip_target_to_draw == :ip

	block_compute_all #unless @offset.abs < 0.001
end

#DRAGGING: Compute the offset when moving
def dragging_offset_compute(offset=nil)
	return unless @target_cur
	@offset = (offset) ? offset : @origin_ini.distance(@target_cur)
	vec = @origin_ini.vector_to(@target_cur)
	@offset = -@offset if vec.valid? && vec % @vector_cur < 0
	@offset_text = (@offset) ? Sketchup.format_length(@offset) : ""
	
	#Notify if offset changed direction
	notify_offset_direction
end

#DRAGGING: Start manually or resume dragging
def dragging_resume
	if @mode == :selection
		dragging_start if @initial_face
	elsif @mode == :preview
		algo_prepare_calculation if @lst_blocks.empty?
		set_state_mode :dragging
	end	
end

#----------------------------------------------------------
# WIREFRAME: Compute the wireframe for the push pull shape
#----------------------------------------------------------

#WIREFRAME: Compute the wireframe
def wireframe_init
	@option_gen_group = option_get :gen_group
	@wireframe_lines_borders = []
	@wireframe_lines_borders_dashed = []
	@wireframe_lines = []
	@wireframe_lines_dashed = []
	@wireframe_lines_junctions = []
	@hwireframe_triangles = {}
	@hwireframe_edges = {}
	@wireframe_lines_bbox = []
end

#WIREFRAME: Compute the wireframe
def wireframe_compute
	wireframe_init
	@lst_blocks.each do |block| 
		tr_slaves = block.grouping.tr_slaves
		if tr_slaves
			tr_inv = block.tr_inv
			tr_slaves.each { |t| wireframe_compute_block block, t * tr_inv }
		else
			wireframe_compute_block block
		end	
	end	
end

#WIREFRAME: Compute the wireframe of a master block
def wireframe_compute_block(block, t=nil)	
	t = @tr_id unless t
	tinv = block.tr_inv
	hpvx = block.hsh_pvx
	bbox = (@option_gen_group && G6.transformation_identity?(t)) ? Geom::BoundingBox.new : nil
	
	#Wireframe for sides
	@option_borders = option_get(:borders)
	forced = (@option_borders == :grid) 
	hpvx.each do |id, pvx|
		next unless pvx.at_border || forced
		next unless pvx.target
		origin = t * pvx.origin
		target = t * pvx.target
		bbox.add tinv * origin, tinv * target if bbox
		if !pvx.dashed
			@wireframe_lines_borders.push origin, target
		else	
			@wireframe_lines_borders_dashed.push origin, target
		end	
	end	
	
	#Wireframe for top faces
	block.lst_peds.each_with_index do |ped, i|
		nmax = 5000
		break if i > nmax
		pvx1 = hpvx[ped.pvx1_id]
		pvx2 = hpvx[ped.pvx2_id]
		next unless pvx1.target && pvx2.target
		pt1 = t * pvx1.target
		pt2 = t * pvx2.target
		bbox.add tinv * pt1, tinv * pt2 if bbox
		if ped.dashed && !forced
			@wireframe_lines_dashed.push pt1, pt2
		else	
			@wireframe_lines.push pt1, pt2
		end	
		if forced && !ped.coface
			pt1 = t * pvx1.origin
			pt2 = t * pvx2.origin
			if ped.dashed
				@wireframe_lines_dashed.push pt1, pt2
			else	
				@wireframe_lines.push pt1, pt2
			end	
		end
	end

	#Wireframe for junctions
	if @jpp_mode == :round && @offset != 0
		ipos = (@offset > 0) ? 0 : 1	
		block.lst_junctions[ipos].each do |junction|
			pts = junction_calculate_points(junction)
			for i in 0..pts.length-2
				@wireframe_lines_junctions.push t * pts[i], t * pts[i+1]
			end	
		end
	end
		
	#Offset surfaces decomposed in triangles
	if G6.su_capa_color_polygon && block.hwireframe_faces && @offset != 0
		block.hwireframe_faces.each do |idcolor, triangles|
			@hwireframe_triangles[idcolor] = [] unless @hwireframe_triangles[idcolor]
			begin
				@hwireframe_triangles[idcolor] += triangles.collect { |id| t * hpvx[id].target }
			rescue
			end
		end	
	end	

	#Border surfaces decomposed in triangles
	if G6.su_capa_color_polygon && @option_borders != :none && block.hwireframe_borders && @offset != 0
		block.hwireframe_borders.each do |idcolor, peds|
			lsw = @hwireframe_triangles[idcolor]
			lsw = @hwireframe_triangles[idcolor] = [] unless lsw
			peds.each do |ped|
				next unless ped.at_border || forced
				pvx1 = hpvx[ped.pvx1_id]
				pvx2 = hpvx[ped.pvx2_id]
				tri1 = [t * pvx1.origin, t * pvx2.origin, t * pvx2.target]
				tri2 = [t * pvx2.target, t * pvx1.target, t * pvx1.origin]
				lsw.push *tri1
				lsw.push *tri2
			end	
		end	
		block.hwireframe_edges.each do |idcolor, lines|
			lsw = @hwireframe_edges[idcolor]
			lsw = @hwireframe_edges[idcolor] = [] unless lsw
			lines.each { |line| lsw.push *(line.collect { |pt| t * pt }) }
		end	
	end	
	
	#Wireframe for bounding box
	if bbox
		ts = block.tr * t * Geom::Transformation.scaling(bbox.center, 1.05)
		@wireframe_lines_bbox.push *(G6.bbox_quads_lines(bbox)[1].collect { |pt| ts * pt })
	end
end

#-------------------------------------------------------------
# MODIF: Handle changes and parameters calculation
#-------------------------------------------------------------

#MODIF: decide whether to go to Preview mode or execute the geometry directly after a modification
def go_geom? ; @nb_total_faces < @param_auto_geom ; end
def go_preview_or_geom
	if go_geom?
		geometry_execute
	else
		set_state_mode :preview
		hiding_manage_faces
	end	
end

#MODIF: Prepare a modification of parameters
def modify_preparation
	return false if @mode == :selection && !@geometry_generated && !@initial_face_cur
	unless @lst_blocks && @lst_blocks.length > 0
		@origin_ini, @normal_ini = visual_compute_origin @view, @xmove, @ymove if @xmove
		selection_freeze_positions
		algo_prepare_calculation
	end
	(@in_error) ? false : true
end

#MODIF: Execute the modification after a change of parameters
def modify_execute(keep_dragging=false)
	#Undo geometry if just being generated
	if @geometry_generated
		@suops.abort_operation
		start_context_operation
		remapping_faces
	end
	
	#Updating the directions and offset at vertices if needed
	block_prepare_vertices_all
	
	#Updating the preview mode
	if modify_preparation
		if @mode == :dragging
			dragging_stop unless keep_dragging
			block_compute_all
		elsif @mode == :preview
			block_compute_all
		else
			block_compute_all
			go_preview_or_geom
		end	
	end
	
	#Updating the hidden faces
	refresh_viewport
end

#MODIF: Modify Offset from the VCB or the dialog box
def modify_offset(offset)
	@offset = offset
	@offset_text = (@offset) ? Sketchup.format_length(@offset) : ""
	notify_offset_direction
	modify_execute
end

#MODIF: Modify Offset from the VCB or the dialog box
def modify_angle_round(angle)
	angle = @angle_round_min if angle < @angle_round_min
	angle = @angle_round_max if angle > @angle_round_max
	@angle_round = angle
	@prepare_vertices_done = false
	modify_execute
end

#MODIF: Modify number of segments for roundings
def modify_segment_round(nb_seg)
	nb_seg = @segment_round_min if nb_seg < @segment_round_min
	nb_seg = @segment_round_max if nb_seg > @segment_round_max
	@segment_round = nb_seg
	@prepare_vertices_done = false
	modify_execute
end

#MODIFY: Modify the privileged plane or axis
def modify_planar_direction
	@palette_message = nil
	compute_vector_lock
	if @jpp_mode == :vector
		@last_vector_cur = nil
	end	
	compute_drawing_param_direction
	return refresh_viewport if @mode == :selection && !@geometry_generated
	
	#Requesting an update of the directions and offset at vertices 
	@prepare_vertices_done = false
	
	#Performing the update
	if @mode == :dragging
		onMouseMove_zero
	else
		modify_execute
	end	
end

#MODIFY: Modify the option for thickening
def modify_option_thickening
	@option_thickening = !option_get(:thickening)
	onSetCursor
	option_set(:thickening, @option_thickening, [:keep_dragging, :shield])
end

#MODIFY: Modify the option for thickening
def modify_option_gen_group
	@option_gen_group = !option_get(:gen_group)
	onSetCursor
	option_set(:gen_group, @option_gen_group, [:keep_dragging, :shield])
end

#MODIF: Notification of change of sign for the offset
def notify_offset_direction
	return if @offset_prev && (@offset_prev * @offset > 0 || @offset_prev != 0 && @offset == 0 || @offset_prev == 0 && @offset != 0)
	@offset_prev = @offset
	algo_notify_offset_direction
end

#HID FACES: Hide or show faces (put them on an invisible layer)
def hiding_manage_faces(show=false)
	return unless @lst_blocks
	tmp_layer = @temp_layer.layer
	option_thickening = option_get :thickening
	
	#Hiding / showing faces
	@lst_blocks.each do |block|
		if show || @option_thickening || block.force_thicken
			block.lst_pfaces.each do |pface|
				face = pface.face
				face.layer = pface.layer
				@selection.add face
			end
			block.hsh_ped.each do |id, ped|
				ped.edge.layer = ped.layer unless ped.at_border
			end	

		else
			block.lst_pfaces.each do |pface|
				face = pface.face
				face.layer = tmp_layer
				@selection.remove face
			end
			block.hsh_ped.each do |ped_id, ped|
				ped.edge.layer = tmp_layer unless ped.at_border
			end	
		end	
	end	
end

#---------------------------------------------------------------------------------------
# Undo Management
#---------------------------------------------------------------------------------------

#UNDO: Cancel and undo methods
def onCancel(flag, view)
	if @dirman.onCancel(flag, view)
		refresh_viewport
		return
	end
	
	case flag
	when 1, 2	#Undo or reselect the tool
		handle_undo
	when 0		#user pressed Escape	
		handle_escape
	end
end

#UNDO: Handle an undo
def handle_undo
	if @mode == :selection
		if @geometry_generated
			@suops.commit_operation
			@geometry_generated = false
			UI.start_timer(0.1) { after_undo_geometry true }
		else
			@suops.abort_operation
			UI.start_timer(0.1) { start_context_operation ; deselection ; onMouseMove_zero }
		end
	else
		UI.start_timer(0.1) { start_context_operation ; handle_escape }
	end	
end

#UNDO: Handle the escape key depending on current mode
def handle_escape(inplace=true)
	case @mode
	when :selection
		if @geometry_generated
			@suops.abort_operation
			after_undo_geometry
			set_state_mode :preview
		else
			@facepicker.history_undo
		end		
	when :preview
		if inplace
			dragging_resume
		else
			set_state_mode :selection
			hiding_manage_faces true
		end	
	when :dragging
		set_state_mode :selection
		hiding_manage_faces true
	end
	onMouseMove_zero	
end

#--------------------------------------------------
# ROLLBACK: Rollback management
#--------------------------------------------------

#ROLLBACK: Check if rollback is allowed
def rollback_allowed?
	@mode != :selection || @geometry_generated || @facepicker.history_undo_possible?
end

#ROLLBACK: Execute the rollback
def rollback_execute(no_undo=false)
	handle_escape false
end

#ROLLBACK: Manage the undo of geometry
def after_undo_geometry(show=false)
	start_context_operation
	remapping_faces true
	hiding_manage_faces show
	onMouseMove_zero
end

#ROLLBACK: Unselect all faces	
def deselection
	@facepicker.selection_unselect_all
end
	
#Check if current contour can be validated
def validate_allowed?
	@mode == :preview
end

#Validate action, depending on the current mode
def execute_validate
	case @mode
	when :selection
		if @geometry_generated && !@initial_face_cur
			exit_tool
		elsif @initial_face_cur
			geometry_commit if @geometry_generated
			set_state_mode :selection
			algo_rewind
			if @offset && @jpp_mode != :vector
				modify_offset @offset
			elsif modify_preparation
				dragging_start
			end	
		end	
	when :dragging
		dragging_stop
	when :preview
		geometry_execute
	end	
end

#Abort the operation and exit
def abort_tool
	@aborting = true
	@suops.abort_operation
	@model.select_tool nil
end

#---------------------------------------------------------------------------------------------
# SU TOOL: Mouse Movement Methods
#---------------------------------------------------------------------------------------------

#SU TOOL: Mouse Movements
def onMouseMove_zero(flag=nil) ; onMouseMove(@flags, @xmove, @ymove, @view) if @xmove ; end
def onMouseMove(flags, x, y, view)
	#Event for the palette
	@xmove = x
	@ymove = y
	@flags = flags
	@initial_face_cur = nil
	
	#Direction manager
	if @dirman.onMouseMove(flags, x, y, view)
		return
	end
	
	#Mouse in palette
	if super
		@mouse_in_palette = true
		onSetCursor
		refresh_viewport
		return	
	end
	@mouse_in_palette = false
	
	@ip_cur.pick view, x, y
	
	#Checking if Start of dragging
	if @mode != :dragging && @xdown && @button_down && ((x - @xdown).abs > @pixel_dragging || (y - @ydown).abs > @pixel_dragging)
		dragging_start if @initial_face && !@in_error
		
	#Synchronize draw and move
	elsif @moving
		return 
		
	#Dragging	
	elsif @mode == :dragging
		dragging_move view, x, y
	
	#Normal Selection mode
	elsif @mode == :selection	
		@facepicker.handle_move(flags, x, y, view)
		@initial_face_cur, @tr_cur, @parent_cur = @facepicker.selection_get_picked_face_info
		@origin_cur, @normal_cur = visual_compute_origin view, x, y
	end
	
	#Refreshing the view
	@moving = true
	refresh_viewport
	onSetCursor
end	

#SU TOOL: Check if the state of mouse is compatible with internal flags
def reconcile_button_state(flags, x, y, view)
	if RUN_ON_MAC
		onLButtonUp(flags, x, y, view) if flags == 256 && @button_down
	else
		onLButtonUp(flags, x, y, view) if flags == 1 && !@button_down
	end
end

#SU TOOL: Mouse leaves the viewport
def onMouseLeave(view)
	@mouseOut = true
	view.invalidate
end

#SU TOOL: Mouse enters the viewport
def onMouseEnter(view)
	@mouseOut = false
	view.invalidate
end
	
#SU TOOL: return the extents for drawing
def getExtents
	@model.bounds
end

def suspend(view)
	@keyman.reset
	@zoom_void.suspend(view, @xmove, @ymove)
end

def resume(view)
	@keyman.reset
	@zoom_void.resume(view)
	refresh_viewport
end
	
#---------------------------------------------------------------------------------------------
# SU TOOL: Click Management
#---------------------------------------------------------------------------------------------

#SU TOOL: Button click DOWN
def onLButtonDown(flags, x, y, view)
	#Interrupting geometry construction if running
	return if @suops.interrupt?
	
	#Direction manager
	return if @dirman.onLButtonDown(flags, x, y, view)
	
	#Palette management
	if super
		refresh_viewport
		return
	end
	
	@button_down = true
	@time_down = Time.now
	@xdown = x
	@ydown = y
	
	#Dispatching the event
	case @mode
	when :selection
		onMouseMove(flags, x, y, view)
		#@face_down = @initial_face_cur
		@face_down, = @facepicker.selection_get_picked_face_info
		if @geometry_generated && !@face_down
			return exit_tool
		else	
			@facepicker.onLButtonDown(flags, x, y, view)
			@origin_ini, @normal_ini = visual_compute_origin view, x, y
			selection_freeze_positions
			algo_rewind
		end	
	when :dragging
	
	when :preview
	
	when :geometry
		
	end	
	onMouseMove_zero
end

#SU TOOL: Button click UP - Means that we end the selection
def onLButtonUp(flags, x, y, view)
	#Direction manager
	return if @dirman.onLButtonUp(flags, x, y, view)
	if super
		@face_down = nil
		onMouseMove_zero
		return
	end
	@button_down = false
	case @mode
	when :selection	
		if @time_down && Time.now - @time_down > 0.6		#long click
			if @face_down									#free dragging
				dragging_start if @vector_cur
			else
				deselection
			end
		else	
			@facepicker.onLButtonUp(flags, x, y, view)
		end	
	when :dragging
		dragging_stop
	when :preview	
		geometry_execute
	end	
	@face_down = nil
	onMouseMove_zero
end

#SU TOOL: Double Click received
def onLButtonDoubleClick(flags, x, y, view)
	return if super
	case @mode
	when :selection, :dragging	
		algo_repeat_pushpull
	end	
	refresh_viewport
end

#---------------------------------------------------------------------------------------------
# KEY: Keyboard handling
#---------------------------------------------------------------------------------------------

#KEY: Return key pressed
def onReturn(view)
	execute_validate
end

#KEY: Manage key events
def key_manage(event, key, view)
	case event
	
	#Passthrough
	when :down
		#Passing the key to the Direction manager	
		return :stop if keydown_for_direction_manager?(key)
		
		#Passing the key to the facepicker	
		return :stop if @mode == :selection && @keyman.ctrl_down? && @facepicker.handle_key_down(key)

	when :up
		#Passing the key to the facepicker	
		return :stop if @mode == :selection && @facepicker.handle_key_up(key)
	
	#Shift key events
	when :shift_down
		compute_palette_message
	when :shift_toggle
		if @jpp_mode == :vector
			vector_constrain
		else	
			@inference_on = !@inference_on
		end	
		compute_palette_message
	when :shift_up
		@otpicker.direction_changed
		
	#Ctrl key events
	when :ctrl_down		
	when :ctrl_toggle
		modify_option_thickening
	when :ctrl_up
	when :ctrl_other_up
		onMouseMove_zero 0
	
	#Backspace
	when :backspace
		if @mode == :selection && !@geometry_generated
			@facepicker.history_clear
		else
			rollback_execute
		end	

	#TAB
	when :tab
		if @mode == :preview
			dragging_resume
		elsif @mode == :selection
			@facepicker.cycle_option_face_selection(!@keyman.shift_down?)
			onMouseMove_zero
		end
	end
end

#KEY: Handle Key down
def onKeyDown(key, rpt, flags, view)
	ret_val = @keyman.onKeyDown(key, rpt, flags, view)
	@view.invalidate
	onSetCursor
	ret_val
end

#KEY: Handle Key up
def onKeyUp(key, rpt, flags, view)
	ret_val = @keyman.onKeyUp(key, rpt, flags, view)
	@view.invalidate
	onSetCursor
	ret_val
end

#KEY: Check the key down for the Direction manager
def keydown_for_direction_manager?(key)
	return false if (@keyman.shift_down? || @keyman.ctrl_down?)
	case key
	when VK_UP
		code = :z
	when VK_DOWN
		code = :no
	when VK_RIGHT
		code = :x
	when VK_LEFT
		code = :y
	else
		return false
	end	
	@dirman.set_direction_code(code, (@keyman.ctrl_down?))
	refresh_viewport
	true
end

#---------------------------------------------------------------------------------------------
# DRAW: Drawing Methods
#---------------------------------------------------------------------------------------------

#DRAW: Draw top method
def draw(view)	
	if @dirman.draw(view)
		return
	end
	
	#Dragging is in error
	if false && @in_error
		super
		@moving = false
		return
	end
	
	#Drawing the curves selected
	case @mode
	when :selection
		unless @mouseOut || @mouse_in_palette
			@facepicker.draw view if @facepicker
			drawing_origin_current view
		end	
	when :dragging, :preview
		drawing_wireframe view
		drawing_target view
	when :geometry
		
	end	
	
	if @in_error
		drawing_in_error view 
	else	
		drawing_messages view
	end	
	drawing_inference view unless @mode == :preview
	drawing_planar_direction view
	
	@moving = false
	
	#Drawing the palette
	super	
end

#DRAW: draw a plane-grid or a vector if there is a planar direction active
def drawing_planar_direction(view)
	return unless @planar_dir && @mode != :geometry
	
	if @mode == :selection
		origin = @ip_origin.position
	elsif @mode == :dragging || @mode == :preview
		origin = @target_cur
	end
	
	return unless origin
	
	if @jpp_mode == :vector
	
	else
		pix = (@mode == :selection) ? 5 : 20
		pix = 5
		vec = @planar_dir
		vec = @tr_cur * vec if @tr_cur && vec && @planar_local
		@shape_grid.draw view, G6.small_offset(view, origin), vec, nil, pix
	end
end

#DRAW: drawing the inference mark
def drawing_inference(view)
	return unless @xmove && visual_inference_on?
	return if @mouseOut || @mouse_in_palette || (@geometry_generated && !@initial_face_cur)
	pts = G6.pts_square @xmove, @ymove+30, 4
	view.drawing_color = 'blue'
	view.draw2d GL_POLYGON, pts
end

#Drawing the custom tooltip message
def drawing_messages(view)
	return if @param_no_tooltip
	return if @mouseOut || @mouse_in_palette
	case @mode
	when :dragging
		if go_geom?
			text = (@button_down) ? @tip_release_geom : @tip_click_release_geom
		else
			text = (@button_down) ? @tip_release_preview : @tip_click_release_preview
		end	
		G6.draw_rectangle_multi_text view, @xmove, @ymove, text, @hsh_boxinfo_preview	
	when :preview
		G6.draw_rectangle_multi_text view, @xmove, @ymove, @tip_generate_geometry, @hsh_boxinfo_geom
	when :selection
		if @geometry_generated && !@initial_face_cur
			G6.draw_rectangle_multi_text view, @xmove, @ymove, @tip_click_exit, @hsh_boxinfo_exit
		end	
	end
end

#Drawing the custom tooltip message
def drawing_in_error(view)
	G6.draw_rectangle_multi_text view, @xmove, @ymove, @tip_in_error, @hsh_boxinfo_error	
end

#DRAW: Drawing a cross at origin
def drawing_origin_current(view)
	#origin, normal = visual_compute_origin view, @xmove, @ymove
	origin, normal = @origin_cur, @normal_cur
	return unless origin && normal && normal.valid?
	
	#Drawing the inference if applicable or a cross at origin
	if @ip_origin_to_draw
		@ip_origin.draw(view)
	else
		axes = normal.axes
		pix = 4
		size = view.pixels_to_model pix, origin
		lines = [origin.offset(axes[0], -size), origin.offset(axes[0], size), origin.offset(axes[1], -size), origin.offset(axes[1], size)]
		view.line_stipple = ''
		view.line_width = 2
		view.drawing_color = 'blue'
		view.draw2d GL_LINES, lines.collect { |pt| view.screen_coords pt }
	end
	
	#Drawing the direction
	pix = 180
	size = view.pixels_to_model pix, origin
	lines = [origin, origin.offset(normal, size)]
	view.line_stipple = '-'
	view.line_width = @planar_line_width
	view.drawing_color = @planar_color
	if @jpp_mode == :vector && @keyman.shift_down? && @vector_cur && @planar_line_width == 1
		view.line_width = 2
	end
	view.draw2d GL_LINE_STRIP, lines.collect { |pt| view.screen_coords pt }	
end

#DRAW: Compute the drawing parameter depending on planar_direction
def compute_drawing_param_direction
	vec = @planar_dir
	vec = @vector_lock unless vec
	if vec
		@planar_line_width = 2
		if vec.parallel?(X_AXIS)
			@planar_color = 'red'	
		elsif vec.parallel?(Y_AXIS)
			@planar_color = 'green'	
		elsif vec.parallel?(Z_AXIS)
			@planar_color = 'blue'
		else	
			@planar_color = (@planar_dir) ? 'orange' : 'black'
		end	
					
	else
		@planar_stipple = '-'
		@planar_line_width = 1
		@planar_color = 'black'
	end
end

#DRAW: Drawing a cross at origin
def drawing_target(view)
	return unless @mode == :dragging && @target_cur
	
	origin2d = view.screen_coords(@origin_ini)
	target2d = view.screen_coords(@target_cur)
	d = origin2d.distance(target2d) + 50

	#Drawing the direction
	pix = 180
	pix = d if pix < d
	vec_guide = origin2d.vector_to target2d
	if vec_guide.valid?
		ptend2d = origin2d.offset vec_guide, pix
		view.line_stipple = '_'
		view.line_width = @planar_line_width
		view.drawing_color = @planar_color
		view.draw2d GL_LINE_STRIP, origin2d, ptend2d
	end
	
	#Drawing a square for the origin
	pts = G6.pts_square origin2d.x, origin2d.y, 4
	view.drawing_color = 'green'
	view.draw2d GL_POLYGON, pts

	#Drawing a line between the origin and target
	if origin2d != target2d
		view.line_stipple = '-'
		view.line_width = @planar_line_width + 1
		view.drawing_color = @planar_color
		view.draw2d GL_LINE_STRIP, origin2d, target2d
	end
	
	#Drawing the inference if applicable
	if @ip_target_to_draw == :ip
		@ip_target.draw(view)
		return if @target_cur == @ip_target.position
	end

	#Target is at origin
	return if @ip_target_to_draw == :origin
	
	#Drawing a square for the target
	pts = G6.pts_square target2d.x, target2d.y, 4
	view.drawing_color = 'red'
	view.draw2d GL_POLYGON, pts
	
	ipos2d = @point2d_picked
	if ipos2d != target2d
		view.drawing_color = 'gray'
		view.line_stipple = '.'
		view.line_width = 1
		view.draw2d GL_LINE_STRIP, ipos2d, target2d
		pts = G6.pts_triangle ipos2d.x, ipos2d.y, 3
		view.drawing_color = 'green'
		view.draw2d GL_POLYGON, pts		
	end	
end

#DRAW: Draw the wireframe
def drawing_wireframe(view)
	#Erasing some lines
	if @hwireframe_edges && @offset > 0
		view.line_width = 1
		view.line_stipple = ''
		@hwireframe_edges.each do |idcolor, ls|
			next if ls.empty?
			color = @hwireframe_colors[idcolor]
			color = @su_face_front_color unless color
			view.drawing_color = color
			view.draw GL_LINES, ls.collect { |pt| G6.small_offset(view, pt) } unless ls.empty?
		end	
	end
	
	#Drawing the sample faces
	if @hwireframe_triangles
		@hwireframe_triangles.each do |idcolor, ls|
			next if ls.empty?
			color = @hwireframe_colors[idcolor]
			color = @su_face_front_color unless color
			view.drawing_color = color
			begin
				view.draw GL_TRIANGLES, ls.collect { |pt| G6.small_offset(view, pt, -1) }
			rescue
				puts "DRAW TRI = #{ls.inspect}"
			end	
		end	
	end
	
	#Drawing the lines
	if @wireframe_lines && !@wireframe_lines.empty?
		view.drawing_color = 'purple'
		view.line_stipple = ''
		view.line_width = 2
		view.draw GL_LINES, @wireframe_lines
	end

	if @wireframe_lines_dashed && !@wireframe_lines_dashed.empty? #&& @rendering_options["DrawHidden"]
		view.drawing_color = 'purple'
		view.line_stipple = '_'
		view.line_width = 1
		view.draw GL_LINES, @wireframe_lines_dashed
	end	
	
	#Drawing the border edges
	view.line_width = 1
	if @wireframe_lines_borders && !@wireframe_lines_borders.empty?
		if @option_borders == :none
			view.drawing_color = 'gray'
			view.line_stipple = '-'
		else
			view.drawing_color = 'black'
			view.line_stipple = ''
		end
		view.draw GL_LINES, @wireframe_lines_borders
	end	
	if @wireframe_lines_borders_dashed && !@wireframe_lines_borders_dashed.empty? #&& @rendering_options["DrawHidden"]
		if @option_borders == :none
			view.drawing_color = 'gray'
			view.line_stipple = '-'
		else
			view.drawing_color = 'black'
			view.line_stipple = '_'
		end
		view.draw GL_LINES, @wireframe_lines_borders_dashed
	end	
	
	#Drawing the junctions
	if @wireframe_lines_junctions && !@wireframe_lines_junctions.empty?
		view.drawing_color = 'purple'
		view.line_stipple = ''
		view.line_width = 1
		view.draw GL_LINES, @wireframe_lines_junctions	
	end
	
	#Drawing the bounding box for group option
	if @wireframe_lines_bbox && @wireframe_lines_bbox.length > 0
		view.drawing_color = 'orangered'
		view.line_stipple = '-'
		view.line_width = 2	
		view.draw GL_LINES, @wireframe_lines_bbox	
	end
end

#---------------------------------------------------------------------------------------------
# SU TOOL: Show Messages
#---------------------------------------------------------------------------------------------

#SU TOOL: Computing Current cursor
def onSetCursor
	#Direction Manager
	ic = @dirman.onSetCursor
	return (ic != 0) if ic
	
	#Palette cursors
	ic = super
	return (ic != 0) if ic
	
	
	#Geometry processing
	if @geometry_processing
		@id_cursor = (@geometry_processing == :red) ? @id_cursor_hourglass_red : @id_cursor_hourglass_green
		return UI.set_cursor(@id_cursor)	
	end
	
	#Other cursors depending on state
	case @mode
	when :selection
		if @geometry_generated && !@initial_face_cur && !@geometry_processing
			@id_cursor = @id_cursor_arrow_exit
		else
			@id_cursor = (@option_thickening) ? @id_cursor_dragging_plus : @id_cursor_dragging
		end	
	when :dragging	
		@id_cursor = (@option_thickening) ? @id_cursor_dragging_plus : @id_cursor_dragging
	when :preview
		@id_cursor = @id_cursor_validate
	end	
	UI.set_cursor @id_cursor
end

#SU TOOL: Refresh the viewport
def refresh_viewport
	show_message
	@view.invalidate
end
	
#SU TOOL: Show message in the palette
def show_message
	#Mouse if either in the palette or out of the viewport
	if @dirman.ask_mode?
		@palette.set_message "Picking Plane direction", 'yellow'
		return
	elsif @mouseOut
		@palette.set_message nil
		return
	elsif @mouse_in_palette
		@palette.set_message @palette.get_main_tooltip, 'aliceblue'
		return
	end	
	
	#Displaying the offset
	if enableVCB? && @offset
		Sketchup.set_status_text @msg_offset, SB_VCB_LABEL
		Sketchup.set_status_text Sketchup.format_length(@offset), SB_VCB_VALUE
	end
	
	#Showing the help text in the status bar
	Sketchup.set_status_text @mode_status_text
	
	#Showing the current options in the palette status bar
	compute_palette_message unless @palette_message 
	@palette.set_message @palette_message, 'palegreen'
end

#SU TOOL: Compute the palette message
def compute_palette_message
	text = @title + ": "
	text += (option_get :thickening) ? @txt_thickening : @txt_push_pulling
	text += " - " + ((visual_inference_on?) ? @txt_inference_on : @txt_inference_off)
	text += " - " + @txt_gen_group if option_get(:gen_group)
	text += " - " + @txt_border_options + ' = ' + @hmsg_borders[option_get(:borders)]
	text += " - " + @dirman.info_text
	@palette_message = text
end

#SU TOOL: Set the text for the information box in the palette
def palette_set_infobox
	if @mode == :selection && @geometry_generated && @time_processing
		text = T6[:MSG_ProcessingTime, sprintf("%0.3f", @time_processing)]
	else
		nbfaces, nbgroups, nbcomps = @facepicker.selection_get_nb_info
		text = " " + T6[:MSG_FacesSelected, nbfaces]
		text2 = " " + T6[:MSG_GroupsSelected, nbgroups] + " - " + T6[:MSG_ComponentsSelected, nbcomps]
		text += "\n" + text2
		@palette.set_tooltip text, 'lightblue'
	end	
end

#--------------------------------------------------
# PALETTE: Callback methods from palette
#--------------------------------------------------

#Notification method from palette for Back and Exec buttons
def notify_from_palette(action, code)
	case action
	when :tip
		compute_tooltip_for_palette code
	when :gray	
		compute_gray_for_palette code
	when :exec
		execute_from_palette code
	end	
end

#Tooltip for Back and Execute buttons
def compute_tooltip_for_palette(code)
	case code
	when :validate
		case @mode
		when :preview
			@tip_generate_geometry
		end	
	when :back
		case @mode
		when :selection
			(@geometry_generated) ? @tip_rollback_to_preview : @tip_rollback_in_selection
		when :preview, :dragging
			@tip_rollback_to_selection
		end	
	else
		""
	end
end

#Gray status for Back and Execute buttons
def compute_gray_for_palette(code)
	case code
	when :validate
		!validate_allowed?		
	when :back
		!rollback_allowed?
	end	
end

#Execution for Back and Execute buttons
def execute_from_palette(code)
	case code
	when :validate
		execute_validate
	when :back
		rollback_execute
	end	
end

#---------------------------------------------------------------------------------------------
#---------------------------------------------------------------------------------------------
# VISUAL: Manage the visual GUI for push pull
#---------------------------------------------------------------------------------------------
#---------------------------------------------------------------------------------------------

#VISUAL: Check if inference is off
def visual_inference_on?
	(@mode == :selection) ? @inference_on && !@keyman.shift_down? : !@keyman.shift_down?
end

#VISUAL: Compute and adjust the normal based on privileged direction
def visual_compute_normal(normal)
	if @planar_dir
		vec = @planar_dir
		vec = @tr_cur * vec if @tr_cur && @planar_local
		vec = vector_adjust_normal normal, vec
		normal = (normal % vec < 0) ? vec.reverse : vec
	elsif @vector_lock
		vec = @vector_lock
		normal = (normal % vec < 0) ? vec.reverse : vec
	end
	normal
end

#VISUAL: Compute the origin, with possible inference
def visual_compute_origin(view, x, y)
	@ip_origin_to_draw = false
	origin, normal = @facepicker.hoverinfo_get_origin
	return nil unless origin
	
	#Adjusting the normal
	normal = visual_compute_normal(normal)
	
	return [origin, normal] if !visual_inference_on?
	
	#Managing inferences
	@ip_origin.pick view, x, y
	dof = @ip_origin.degrees_of_freedom
	if @ip_origin.face && (dof == 0 || (dof == 1 && @ip_origin.edge)) && @ip_origin.display?
		origin = @ip_origin.position
		view.tooltip = "  " + @ip_origin.tooltip
		@ip_origin_to_draw = true
	end	
	[origin, normal]
end

#Calculate the vector Lock
def compute_vector_lock
	if @planar_dir
		@vector_lock = @planar_dir
		@vector_lock = G6.transform_vector(@vector_lock, @tr) if @dirman.get_scope_local
	else
		@vector_lock = nil
	end	
end

#VISUAL: constrain the direction vector
def vector_constrain
	return unless @jpp_mode == :vector
	if @vector_lock
		@vector_lock = nil 
		@dirman.set_direction_code :no
	else
		@vector_lock = @vector_cur 
	end
	compute_drawing_param_direction	
end

#VISUAL: Compute the target and direction vector
def visual_compute_target_vector(view, x, y)
	#Picking the point on screen
	@ip_target.pick view, x, y
	@point2d_picked = Geom::Point3d.new x, y, 0
	
	ip = @ip_target
	vec = @normal_ini
	origin = @origin_ini
	
	#Calculating the vector
	if @jpp_mode == :vector
		compute_vector_lock unless @vector_lock
		if @vector_lock
			vec = @vector_lock
		elsif @keyman.shift_down? && @vector_cur
			vec = @vector_cur
		else
			target = @ip_target.position
			vec = @origin_ini.vector_to target
			return [target, vec]
		end	
		
	elsif @jpp_mode == :extrude
		vec = @block_cur.vector_extrude
		
	end
	
	#Target is located at origin
	inference_ip = nil
	if !vec.valid?
		return nil
		
	elsif @origin_ini && ((ip.position == @origin_ini) || view.screen_coords(ip.position).distance(view.screen_coords(@origin_ini)) < 3)
		target = @origin_ini
		@ip_target_to_draw = :origin
		
	#Target along the vector
	elsif vec.parallel?(@origin_ini.vector_to(ip.position))
		target = ip.position
		@ip_target_to_draw = :ip
		
	#Other cases: Point not inferred
	else
		inference_ip = ip if visual_inference_on? && (ip.degrees_of_freedom == 0)
		pvorig = view.screen_coords @origin_ini
		pv0 = view.screen_coords @origin_ini.offset(vec, 100)
		pv1 = @point2d_picked.project_to_line [pvorig, pv0]
		a = Geom.closest_points [@origin_ini, vec], view.pickray(pv1.x, pv1.y)
		target = a[0]
		@ip_target_to_draw = :screen
	end
	
	[target, vec, inference_ip]
end

#---------------------------------------------------------------------------------------------
#---------------------------------------------------------------------------------------------
# VCB: Handling VCB inputs
#---------------------------------------------------------------------------------------------
#---------------------------------------------------------------------------------------------
	
#VCB: Context in which VCB is enabled (at least faces selected or a previous operation done)	
def enableVCB?
	true
	#@offset || (@mode == :selection && @facepicker.selection_get_picked_face_info[0])
end
	
def enab 
	@offset || (@mode == :selection && @facepicker.selection_get_picked_face_info[0])
end
	
#VCB: Declare vcb formats
def init_VCB	
	@vcb = Traductor::VCB.new
	@vcb.declare_input_format :offset, "l"					#offset length
	@vcb.declare_input_format :angle_round, "a"				#angle round for round mode
	if @jpp_prop_radial_scaling
		@vcb.declare_input_format :radial_scaling, "f__s"	#radial scaling for normal or extrude mode
	elsif @jpp_mode == :round
		@vcb.declare_input_format :segment_round, "i__s"	#segment round for round mode
	end	
end

#VCB: Handle VCB input
def onUserText(text, view)
	return UI.beep unless @vcb
	nb_errors = @vcb.process_parsing(text)
	
	if nb_errors > 0
		lmsg = []
		@vcb.each_error { |symb, s| lmsg.push "<#{s}>" }
		msg = "#{T6[:T_ERROR_InVCB]} \"#{text}\"  --> #{lmsg.join(' - ')}"
		@palette.set_message msg, 'E'
		view.invalidate
	else
		action_from_VCB
	end	
end

#VCB: Execute actions from the VCB inputs for the grid dimensions	
def action_from_VCB
	offset = angle_round = segment_round = radial_scaling = nil
	@vcb.each_result do |symb, val, suffix|
		case symb
		when :offset
			offset = val
		when :angle_round
			angle_round = val
		when :segment_round
			segment_round = val
		when :radial_scaling
			radial_scaling = val
		end	
	end
	
	#Changing the offset
	if offset
		UI.beep if offset == 0	
		modify_offset offset if offset.abs > 0.001 #&& offset != @offset
	end

	#Changing the round angle (Round mode)
	if angle_round
		if @jpp_mode == :round
			modify_angle_round angle_round if angle_round != @angle_round
		else
			UI.beep
		end	
	end

	#Changing the number of segments for rounding (Round mode)
	if segment_round
		if @jpp_mode == :round
			modify_segment_round segment_round if segment_round != @segment_round
		else
			UI.beep
		end	
	end
	
	#Changing the radial scaling
	if radial_scaling
		option_set :radial_scaling, radial_scaling
	end
	
	refresh_viewport
end

end	#class JointPushPullTool

end	#End Module F6_JointPushPull
