###--------------------------------------------------------------------------------
#        Name : ExtrudeAlongPath
#
# Description : Creates rectangular faced 'followme' extrusions along a path - 
#		Use it to make walls, fascias, soffits, frames, rails etc,
#		starting from a selection of joined edges
#
#      Author : TIG (c) 7/2005
#		based on an original wall making idea by Didier Bur
#		and using some vertex array ideas from Rick Wilson
#
#       Usage : Select joined lines, arcs, circles, curves, etc.
#		Select "Extrude Along Path" from the Plugins menu. 
#		In the dialog enter Alignment, Width and Height, and then OK.
#
#		The 4 alignments are - 'Edge' [on one of the plan edges], 'Central(Plan)'
#		[centrally on plan], 'Centroid' [centered on the start face] or 'Central(Side)'
#		[centered on a side face of the extrusion]...
#		To extrude below the selected path enter a negative height (e.g. -1000).
#		It is difficult to know in advance which of the two ends of the selected path
#		will be taken as the path's start, so the concept of left/right alignment
#		is not used.  With the 'Edge' and 'Central(Side)' alignments the extrusion goes
#		on to the path's left side, looking in the direction of the path's first vector.
#		To get an extrusion flipped on to other side of the path choose 'No' when asked
#		if the starting face's side is acceptable...  Note that entering a negative
#		width will flip the starting face's default side...
#
#		The start face is then shown highlighted at one end of the path. In a dialog
#		you are asked if the extrusion should start at that end - pick 'Yes' to keep it
#		there or pick 'No' to move it to the other end.  This is useful if a path has 
#		say one level end and one sloping end, since from which end the extrusion starts
#		will affect its form and 'skewing', as with normal 'FollowMe' use...
#		With the 'Edge' and 'Central(Side)' alignments the start face remains highlighted 
#		at the selected path end and a dialog asks for 'Yes' to confirm its side of the 
#		path OR 'No' to flip it on to the other side of the path (right/left).
#
#		A construction line is drawn along the starting edge to show where the extrusion
#		is relative to the selected path - note that its line-style varies depending on 
#		the alignment...
#
#		The extrusion is then made and grouped. The extrusion is grouped so it does not 
#		interact with adjacent surfaces - afterwards just explode it if appropriate. 
#
#		The selected path remains highlighted after the extrusion is made -
#		you can use this to delete or move unwanted paths etc.
#		The path stays selected even after an undo or exit on an error.
#
#		The entered height is measured perpendicular to the the first vector in the path
#		(in the plane of the blue/Z-axis), so with sloping paths it might give unexpected
#		extrusion dimensions - see above about choosing a path end from which to start...
#		A vertical starting path vector is allowed BUT note that the entered 'height'
#		and 'width' will default to the red/X-axis and green/Y-axis respectively...
#
#		The end faces always start and end perpendicular to the path vector as with the
#		standard 'FollowMe' use.  With sloping paths this can give unexpected (but correct) 
#		'skewed' extrusions in the blue/Z-axis...
#		Heights and widths that are less than the length of pieces of the path's edge 
#		will give correct but unexpected extrusions, which need manually tidying...
#		Closed loop paths are fully extruded, but some lines might need manually tidying,
#		this is because like 'FollowMe' sloping paths do not always close neatly and the 
#		extrusion can 'skew' in the blue/Z-axis.  
#		Multiple arc and other complex sloping 3D paths and alignments might give unexpected
#		extrusion results.  
#
#		Heights and widths cannot be zero and will return an error.
#		A branching path returns an error as 'FollowMe' can't decide which path to take.
#		Selected edges that are not joined (i.e. they don't allhave common ends-starts)
#		will not be extruded - only the first edge or joined group of edges will extrude.
#
#
#        Type : tool
#
#     Version : 1.0  30/7/5	first release.
#		1.1  1/8/5	face edged to avoid v5 followme bug etc.
#		1.2  7/8/5	extrusion always perpendicular to path, left/right toggle,
#				vertical starting path now allowed; extrusion grouped
#				to avoid rare coincident-face bug-splats.
#		1.3  17/8/5	path end start choice added, cline stipple changed,
#				rare inside out extrusions corrected, dialogs revamped.
#
###--------------------------------------------------------------------------------

require 'sketchup.rb'

### #################################################################

def extrude_along_path

@error=0 ### 17/8/5 ###
model = Sketchup.active_model
model.start_operation "extrude_along_path"
entities = model.active_entities
ss = model.selection

### show VCB and status info
Sketchup::set_status_text("Extrude Along Path... PARAMETERS...", SB_PROMPT)
Sketchup::set_status_text(" ", SB_VCB_LABEL)
Sketchup::set_status_text(" ", SB_VCB_VALUE)
###
if ss.empty?
  Sketchup::set_status_text("Extrude Along Path... NO SELECTION ! ", SB_PROMPT)
  UI.messagebox("No Selection to Extrude.")
  return nil
end
### #################################################################

def get_vertices

### this next bit is mainly thanks to Rick Wilson's weld.rb ###

	model=Sketchup.active_model
	ents=model.active_entities
	@sel=model.selection
	sl=@sel.length
	verts=[]
	edges=[]
	@newVerts=[]
	startEdge=startVert=nil

#DELETE NON-EDGES, GET THE VERTICES

	@sel.each {|item| edges.push(item) if item.typename=="Edge"}
	edges.each {|edge| verts.push(edge.vertices)}
	verts.flatten!

#FIND AN END VERTEX

	vertsShort=[]
	vertsLong=[]
	vertsEnds=[] ### 17/8/5 ### to ensure array is only ends ###
	verts.each do |v|
		if vertsLong.include?(v)
			vertsShort.push(v)
		else
			vertsLong.push(v)
		end
	end
	vertsLong.each do |v| ### 17/8/5 ### to ensure array is only ends ###
		if not vertsShort.include?(v)
			vertsEnds.push(v)
		end
	end ### 17/8/5 ### to ensure array is only ends ###
	if vertsEnds.length==0 ### i.e. it's looped ###
 		### path start or end ? ### 17/8/5 ###
		if @theEnd==0
		  startVert=vertsLong.first
		  startEdge=startVert.edges.first
		else
		  startVert=vertsLong.last
		  startEdge=startVert.edges.first
		end
		###
		closed=true
	else
		if vertsEnds.length != 2
			Sketchup::set_status_text("Extrude Along Path... PATH ERROR ! ", SB_PROMPT)
			UI.messagebox("The selected Path either branches or is not continuous ! \nCannot make an Extrusion ! \nRe-select a single continuous path... ") ### 17/8/5 ###
			@error=1
			return nil
		else
			### path start or end ? ### 17/8/5 ###
			if @theEnd==0
			  startVert=vertsEnds.first
			else
			  startVert=vertsEnds.last
			end
			###
			closed=false
			startEdge=startVert.edges.first
		end
	end
	@sel.clear


#SORT VERTICES, LIMITING TO THOSE IN THE SELECTION SET

	if startVert==startEdge.start
		@newVerts=[startVert]
		counter=0
		while @newVerts.length < verts.length
			edges.each do |edge|
				if edge.end==@newVerts.last
					@newVerts.push(edge.start)
				elsif edge.start==@newVerts.last
					@newVerts.push(edge.end)
				end
			end
			counter+=1
			if counter > verts.length
				Sketchup::set_status_text("Extrude Along Path... ERROR ! ", SB_PROMPT)
				return nil if UI.messagebox("There seems to be a problem. Try again?", MB_YESNO)!=6
				@newVerts.reverse!
				reversed=true
			end
		end
	else
		@newVerts=[startVert]
		counter=0
		while @newVerts.length < verts.length
			edges.each do |edge|
				if edge.end==@newVerts.last
					@newVerts.push(edge.start)
				elsif edge.start==@newVerts.last
					@newVerts.push(edge.end)
				end
			end
			counter+=1
			if counter > verts.length
				Sketchup::set_status_text("Extrude Along Path... ERROR ! ", SB_PROMPT)
				return nil if UI.messagebox("There seems to be a problem. Try again?", MB_YESNO)!=6
				@newVerts.reverse!
				reversed=true
			end
		end
	end
	@newVerts.reverse! if reversed

#CONVERT VERTICES TO POINT3Ds

	@newVerts.collect!{|x| x.position}

### now have an array of vertices in order with NO forced closed loop ...

end ### get_vertices

### near or far ?
@theEnd=0
get_vertices
if @error==1
  return nil
end

### dialog #################################################################

alignments = ["Edge","Central(Plan)","Centroid","Central(Side)"]
enums = [alignments.join("|")]
prompts = ["Alignment: ","Width: ","Height: "]

if not $widthIn
   values = ["Edge",100.mm,1000.mm]
else
   values = [$alignment,$widthIn,$heightIn]
end

results = inputbox prompts, values, enums, "Face Parameters (mm)"

return nil if not results ### i.e. the user cancelled the operation

$alignment,$widthIn,$heightIn = results

### #################################################################

### #################################################################

### restore selection set of edges and display them
def edge_reselector (xnewVerts)
 model = Sketchup.active_model
 ents=model.active_entities
 theEdgeX = []
 0.upto(xnewVerts.length-2) do |i| ### 1/8/5
   theEdgeX[i] = ents.add_line(xnewVerts[i],xnewVerts[i+1])  ### make vertices into edges
 end
 model.selection.clear
 model.selection.add theEdgeX
end
###def


### do main stuff #################################################################

pt1 = @newVerts[0]
pt2 = @newVerts[1]

width = $widthIn
if $widthIn == 0.mm ### can't be 0 ###
   $widthIn = 100.mm
   edge_reselector(@newVerts)
   UI.messagebox("Zero Width NOT allowed ! ")
   return nil
end
###
if $heightIn == 0.mm ### can't be 0 ###
   $heightIn = 1000.mm
   edge_reselector(@newVerts)
   UI.messagebox("Zero Height NOT allowed ! ")
   return nil
end
###
if (pt1.x == pt2.x) and (pt1.y == pt2.y) ### vertical 1st path ###
   vflag = 1
   height = $heightIn
else
   vflag = 0
   height = (($heightIn * (pt1.distance pt2)) / (pt1.distance [pt2.x,pt2.y,pt1.z]))
end
###
if $alignment == "Edge"
   offL = width
   offR = 0
   heightUp = height
   heightDn = 0
   cpatt = "__" ### 17/8/5 ###
end
if $alignment == "Central(Plan)"
   offL = width / 2
   offR = width / 2
   heightUp = height
   heightDn = 0
   cpatt = "." ### 17/8/5 ###
end
if $alignment == "Central(Side)"
   offL = width
   offR = 0
   heightUp = height / 2
   heightDn = height / 2
   cpatt = "_" ### 17/8/5 ###
end
if $alignment == "Centroid"
   offL = width / 2
   offR = width / 2
   heightUp = height / 2
   heightDn = height / 2
   cpatt = "-.-" ### 17/8/5 ###
end
### #################################################################

if vflag == 1 ### vertical ### 7/8/5
  vec = pt1.vector_to [(pt1.x + 1), pt1.y, pt1.z]
else
  vec = pt1.vector_to [pt2.x, pt2.y, pt1.z]
end

### #################################################################

piBy2 = 1.5707963267948965 ### a radian right-angle for calculating vector offset to edges

rotated_vecL = vec.transform(Geom::Transformation.rotation(pt1, [0,0,1], (0 + piBy2))) ### 7/8/5
pt1_leftC = pt1.offset(rotated_vecL, offL)
rotated_vecR = vec.transform(Geom::Transformation.rotation(pt1, [0,0,1], (0 - piBy2))) ### 7/8/5
pt1_rightC = pt1.offset(rotated_vecR, offR)

if vflag == 1 ### 1st path is vertical ### 7/8/5
  pt1_left  = pt1_leftC.offset([-1,0,0], heightDn)
  pt1_right = pt1_rightC.offset([-1,0,0], heightDn)
  pt2_left  = pt1_leftC.offset([1,0,0], heightUp)
  pt2_right = pt1_rightC.offset([1,0,0], heightUp)
else
  pt1_left  = pt1_leftC.offset([0,0,-1], heightDn)
  pt1_right = pt1_rightC.offset([0,0,-1], heightDn)
  pt2_left  = pt1_leftC.offset([0,0,1], heightUp)
  pt2_right = pt1_rightC.offset([0,0,1], heightUp)
end

###

ents=entities

### #################################################################
### add construction line along first vector ### 7/8/5
cline1 = ents.add_cline(pt1,pt2)
cline1.end=nil
cline1.start=nil
cline1.stipple=cpatt ### 17/8/5 ###


### group the extrusion... ### 7/8/5 #################################################################

group=entities.add_group
entities=group.entities

theFace = entities.add_face(pt1_right, pt1_left, pt2_left, pt2_right) ### 7/8/5

if vflag == 1 and height > 0
   theFace.reverse!
end
if vflag == 1 and width < 0
   theFace.reverse!
end
if vflag == 1 and pt1.z > pt2.z
   theFace.reverse!
end

### check start /end of path ###
keypress = 6 ### = YES
### ask if face starts at correct side ? ### 17/8/5 ###
model.selection.clear
model.selection.add [theFace]
model.selection.add [theFace.edges]
###
Sketchup::set_status_text("Extrude Along Path... END ? ", SB_PROMPT)
keypress = UI.messagebox("Start the Face at this End ? ", MB_YESNO) ###
if keypress != 6
  ### get rid of old stuff first ###
  entities.erase_entities cline1
  entities.erase_entities model.selection
  edge_reselector(@newVerts)
  @theEnd=1
  get_vertices
  ###
  pt1 = @newVerts[0]
  pt2 = @newVerts[1]
  if (pt1.x == pt2.x) and (pt1.y == pt2.y) ### vertical 1st path ###
     vflag = 1
     height = $heightIn
  else
     vflag = 0
     height = (($heightIn * (pt1.distance pt2)) / (pt1.distance [pt2.x,pt2.y,pt1.z]))
  end
  if vflag == 1 ### vertical ### 7/8/5
    vec = pt1.vector_to [(pt1.x + 1), pt1.y, pt1.z]
  else
    vec = pt1.vector_to [pt2.x, pt2.y, pt1.z]
  end
  rotated_vecL = vec.transform(Geom::Transformation.rotation(pt1, [0,0,1], (0 + piBy2))) ### 7/8/5
  pt1_leftC = pt1.offset(rotated_vecL, offL)
  rotated_vecR = vec.transform(Geom::Transformation.rotation(pt1, [0,0,1], (0 - piBy2))) ### 7/8/5
  pt1_rightC = pt1.offset(rotated_vecR, offR)
  if vflag == 1 ### 1st path is vertical ### 7/8/5
    pt1_left  = pt1_leftC.offset([-1,0,0], heightDn)
    pt1_right = pt1_rightC.offset([-1,0,0], heightDn)
    pt2_left  = pt1_leftC.offset([1,0,0], heightUp)
    pt2_right = pt1_rightC.offset([1,0,0], heightUp)
  else
    pt1_left  = pt1_leftC.offset([0,0,-1], heightDn)
    pt1_right = pt1_rightC.offset([0,0,-1], heightDn)
    pt2_left  = pt1_leftC.offset([0,0,1], heightUp)
    pt2_right = pt1_rightC.offset([0,0,1], heightUp)
  end
  ### add NEW construction line along NEW first vector ### 17/8/5
  cline1 = ents.add_cline(pt1,pt2)
  cline1.end=nil
  cline1.start=nil
  cline1.stipple=cpatt
  ###
  theFace = entities.add_face(pt1_right, pt1_left, pt2_left, pt2_right) ###
if vflag == 1 and height > 0
   theFace.reverse!
end
if vflag == 1 and width < 0
   theFace.reverse!
end
if vflag == 1 and pt1.z > pt2.z
   theFace.reverse!
end

end
### end sorting start / end of path ### 17/8/5 ###


###
keypress = 6 ### = YES
### ask if face starts at correct side ? ### 7/8/5
if $alignment == "Edge" or  $alignment == "Central(Side)"
  model.selection.clear
  model.selection.add [theFace]
  model.selection.add [theFace.edges]
  Sketchup::set_status_text("Extrude Along Path... SIDE ? ", SB_PROMPT)
  keypress = UI.messagebox("Start the Face at this Side ? ", MB_YESNO) ###
end

### if NO then reverse face side ### 7/8/5
if keypress != 6
  entities.erase_entities model.selection
  offR = offR * -1
  offL = offL * -1
  rotated_vecL = vec.transform(Geom::Transformation.rotation(pt1, [0,0,1], (0 + piBy2))) ### 7/8/5
  pt1_leftC = pt1.offset(rotated_vecL, offL)
  rotated_vecR = vec.transform(Geom::Transformation.rotation(pt1, [0,0,1], (0 - piBy2))) ### 7/8/5
  pt1_rightC = pt1.offset(rotated_vecR, offR)
 if vflag == 1 ### 1st path is vertical ### 7/8/5
  pt1_left  = pt1_leftC.offset([-1,0,0], heightDn)
  pt1_right = pt1_rightC.offset([-1,0,0], heightDn)
  pt2_left  = pt1_leftC.offset([1,0,0], heightUp)
  pt2_right = pt1_rightC.offset([1,0,0], heightUp)
 else
  pt1_left  = pt1_leftC.offset([0,0,-1], heightDn)
  pt1_right = pt1_rightC.offset([0,0,-1], heightDn)
  pt2_left  = pt1_leftC.offset([0,0,1], heightUp)
  pt2_right = pt1_rightC.offset([0,0,1], heightUp)
 end
  theFace = entities.add_face(pt1_right, pt1_left, pt2_left, pt2_right)
  theFace.reverse!
 if vflag == 1 and height < 0
   theFace.reverse!
 end
 if vflag == 1 and width < 0
   theFace.reverse!
 end
end
### #################################################################

@@theEdges= []
  0.upto(@newVerts.length-2) do |i| ### 1/8/5
  @@theEdges[i] = ents.add_line(@newVerts[i],@newVerts[i+1]) ### make vertices into edges
end

### follow me along selected edges

if height > 0
  if width > 0
    theExtrusion=theFace.reverse!.followme @@theEdges ###
  end
  if width < 0
    theExtrusion=theFace.followme @@theEdges ###
  end
end
if height < 0
  if width > 0
    theExtrusion=theFace.followme @@theEdges ###
  end
  if width < 0
    theExtrusion=theFace.reverse!.followme @@theEdges ###
  end
end

###
if not theExtrusion
  Sketchup::set_status_text("Extrude Along Path... EXTRUSION ERROR ! ", SB_PROMPT)
  UI.messagebox("NO Extrusion was made ! \nThe selected Path probably branches ! \nRe-select a single continuous path... ")
  entities.erase_entities theFace.edges
  entities.erase_entities cline1
  edge_reselector(@newVerts)
  return nil
end
###

model.commit_operation

### #################################################################
### restore selection set of edges and display them
edge_reselector(@newVerts)


end ### end def
### #################################################################

### menu bits  #################################################################

if( not file_loaded?("ExtrudeAlongPath.rb") )###

   UI.menu("Plugins").add_separator
   UI.menu("Plugins").add_item("Extrude Along Path") { extrude_along_path }

end

###
file_loaded("ExtrudeAlongPath.rb")###
### #################################################################
