#------------------------------------------------------------------------------------------------
# Permission to use, copy, modify, and distribute this software for 
# any purpose and without fee is hereby granted.
#------------------------------------------------------------------------------------------------
# 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.
# -----------------------------------------------------------------------------------------------
# THIS PLUGIN WAS DEVELOPED AND TESTED UNDER WINDOWS VISTA ONLY AND MAY OR MAY NOT WORK ON A MAC. 
# -----------------------------------------------------------------------------------------------
#    Name:	Multiple Offsets
#      By:	sdmitch
#   Usage:	Create multiple offsets to a selected face(s) using one of the following options.
#						Constant: Input number of offsets, distance between offsets, direction to
#							offset, either inside, outside, or both, and whether the selected face(s) and all 
#							created faces are to be deleted, select No to keep faces.
#						Formula: Enter number of offsets, distance to first offset, direction to offset,
#							delete faces Yes or No, select operator + or *, and value to add or multiply by.
#						List: Select direction of offsets, delete faces Yes or No, a list of offsets seperated
#							by commas, choose type of offset either absolute value or a delta to be added to the
#							last offset value.  If the offsets are a repeated sequence, end the list with *X,
#							where X = the number of repititions for the offsets entered.
#		 Note:	This version does not address the issue of overlap of "Inside" offsets.
#    Date:	Feb 2012
#------------------------------------------------------------------------------------------------
#
require 'Sketchup'
#
if not file_loaded?(File.basename(__FILE__))
    UI.menu('Plugins').add_item('Multiple Offsets') { SDM_Multiple_Offsets.do_offsets }
	 file_loaded(File.basename(__FILE__))
end
#
module SDM_Multiple_Offsets

	def self.do_offsets
		@mod=Sketchup.active_model
		@ent=@mod.active_entities
		@sel=@mod.selection
		unless @sel.empty?
			@method = "Constant" if !@method
			inp=UI.inputbox(["Offset Method:"],[@method],["Constant|Formula|List"],"Multiple Offsets By:")
			if inp
				@mod.start_operation("Multiple Offsets",true); @method = inp[0]
				@existing_faces=[]; @selected_faces=[]
				@ent.each{|f| @existing_faces<<f if f.is_a?(Sketchup::Face)}# collect all existing faces
				@sel.each{|f| @selected_faces<<f if f.is_a?(Sketchup::Face)}# collect all selected faces
				case @method
					when "Constant" ; self.do_offsets_constant
					when "Formula" ; self.do_offsets_formula
					when "List" ; self.do_offsets_list
				end
				if @del == "Yes"
					@selected_faces.each{|f| @ent.erase_entities(f)} # erase selected faces
					@ent.each{|f| @ent.erase_entities(f) if f.is_a?(Sketchup::Face) && !@existing_faces.include?(f)} # erase new faces
				else
					@sel.each{|e| e.find_faces if e.class==Sketchup::Edge and e.curve} # create faces for offsets
				end
				@sel.clear; @mod.commit_operation
			end
		else
			UI.messagebox "Nothing selected.  Select face(s) to be offset"
		end
	end

	def self.do_offsets_constant
		@cnt = 1 if !@cnt; @dir = "Outside" if !@dir; @dis = 1.inch if !@dis; @del = "No" if !@del
		prompts = ["  Number:","Distance:","Direction:","Del Face:"]
		inp=UI.inputbox(prompts,[@cnt,@dis,@dir,@del],[nil,nil,"Inside|Outside|Both","Yes|No"],"Multiple Offsets by Constant")
		if !inp then;return;end
		@cnt = inp[0].to_i; @dis=inp[1].to_l; @dir = inp[2]; @del = inp[3];@sel.clear
		for f in @selected_faces
			osd = @dis; osd = -osd if @dir == "Inside" 
			for i in 1..@cnt
				f.moffset(-osd*i,@ent,@sel) if @dir == "Both"
				f.moffset(osd*i,@ent,@sel)
			end
		end
	end

	def self.do_offsets_formula
		@cnt = 1 if !@cnt; @dir = "Outside" if !@dir; @dis = 1.inch if !@dis; @del = "No" if !@del; @opr = "+" if !@opr; @val = 1.inch if !@val
		prompts = [" Number:","Distance:","Direction:","Del Face:","Operator:","Add/Mult:"]
		inp=UI.inputbox(prompts,[@cnt,@dis,@dir,@del,@opr,@val],[nil,nil,"Inside|Outside|Both","Yes|No","+|*"],"Multiple Offsets by Formula")
		if !inp then;return;end
		@cnt = inp[0].to_i; @dis=inp[1].to_l; @dir = inp[2]; @del = inp[3]; @opr = inp[4]; @val = inp[5].to_l;@sel.clear
		for f in @selected_faces
			osd = @dis; osd = -osd if @dir == "Inside" 
			for i in 1..@cnt
				f.moffset(-osd,@ent,@sel) if @dir == "Both"
				f.moffset(+osd,@ent,@sel)
				case @opr
					when "+"
						osd = osd + @val if @dir != "Inside"
						osd = osd - @val if @dir == "Inside"
					when "*"
						osd = osd * @val
				end
			end
		end
	end

	def self.do_offsets_list
		@dir = "Outside" if !@dir; @del = "No" if !@del; @lst = "1,2,3,4,5,6,7,8,9" if !@lst; @ost = "Absolute" if !@ost
		prompts = ["Direction:","Del Face:","   Offsets:"," OS Type:"]
		inp=UI.inputbox(prompts,[@dir,@del,@lst,@ost],["Inside|Outside|Both","Yes|No",nil,"Absolute|Delta"],"Multiple Offsets by List")
		if !inp then;return;end
		@dir = inp[0]; @del = inp[1]; @lst = inp[2]; @ost = inp[3]; osd,rep=@lst.split("*");dis=osd.split(","); 
		(cnt = rep.to_i * dis.length-dis.length;@ost="Delta";for i in 0...cnt;dis.push dis[i];end) if rep
		@cnt = dis.length;@sel.clear; 
		for f in @selected_faces
			tosd = 0
			for i in 0...@cnt
				(@ost == "Absolute") ? (osd = dis[i].to_l) : (tosd += dis[i].to_l; osd=tosd)
				osd = -osd if @dir == "Inside" 
				f.moffset(-osd,@ent,@sel) if @dir == "Both"
				f.moffset(+osd,@ent,@sel)
			end
		end
	end

	# Code copied from offset.rb
	# Copyright 2004,2005,2006,2009 by Rick Wilson - All Rights Reserved

	class Sketchup::Face
		def moffset(dist,ent,sel)
			begin
				pi = Math::PI
				if (not ((dist.class==Fixnum || dist.class==Float || dist.class==Length) && dist!=0))
					return nil
				end
				verts=self.outer_loop.vertices;pts = [];vecs = []
				verts.reverse! if self.outer_loop.edges[0].reversed_in?(self)
				# CREATE ARRAY pts OF OFFSET POINTS FROM FACE
				0.upto(verts.length-1) do |a|
					vec1 = (verts[a].position-verts[a-(verts.length-1)].position).normalize
					vec2 = (verts[a].position-verts[a-1].position).normalize
					vec3 = (vec1+vec2).normalize
					if vec3.valid?
						ang = vec1.angle_between(vec2)/2
						ang = pi/2 if vec1.parallel?(vec2)
						vec3.length = dist/Math::sin(ang)
						vecs << vec3
						t = Geom::Transformation.new(vec3)
						if pts.length > 0
							vec4 = pts.last.vector_to(verts[a].position.transform(t))
							if vec4.valid?
								unless (vec2.parallel?(vec4))
									t = Geom::Transformation.new(vec3.reverse)
								end
							end
						end
						pts.push(verts[a].position.transform(t))
					else
						puts "#{a} - vec3 is invalid"
					end
				end
				# CHECK FOR DUPLICATE POINTS IN pts ARRAY
				duplicates = []
				pts.each_index do |a|
					pts.each_index do |b|
						next if b==a
						duplicates<<b if pts[a]===pts[b]
					end
					break if a==pts.length-1
				end
				duplicates.reverse.each{|a| pts.delete(pts[a])}
				# CREATE CURVE FROM POINTS IN pts ARRAY
				(pts.length > 2) ? (pts.push pts[0];cur = ent.add_curve(pts);sel.add cur[0]) : (return nil)
			rescue
				puts "#{self} did not offset: #{pts}"
				return nil
			end
		end
	end
end
