=begin 
TIG (c) 2012-2013
All Rights Reserved.
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.

  TIG-Smart_offset.rb

Offsets edges of a specified face by a distance 'smartly'

  TIG::Smart_offset.new(face, distance)

Overview:
A +ve distance offsets the face's outer_loop outside of the loop.
A -ve distance offsets  the face's outer_loop inside the loop.
If the face's outer_loop perimeter contains a thin inlet or thin peninsula 
then twisted loops are avoided and trimmed off, only a single non-twisted 
new face and edge-set is created.
New edges crossing other coplanar faces will split them as appropriate.
Holes in the face or unconnected but coplanar faces that overlap with the new 
geometry might be 'infilled', just as with the native 'offset'.
On completion it returns an array containing the original face and any new 
face[s] created or affected by the creation of the new geometry, or 'nil' 
if no face could be created - e.g. the face was too small to contain the 
internal offset loop using the given -ve distance.
The new face[s] have the same front/back material and layer as the face
itself.
Some very convoluted faces with 're-entrant' forms and 'inlets' can cause 
failures, because a fully 'neatened' set of offset edges cannot be determined...

Usage:
For developers it can be used as a method to call from within their own scripts.  
Add a "require 'TIG-Smart_offset.rb'" line into your code to ensure it's 
loading, OR adjust the [modified] code to run only within your own Module/Class.
Remember to distribute this file with your scripts, because users probably won't 
have it installed.

  f=Sketchup.active_model.selection[0]; TIG::Smart_offset.new(f, 1.2)

OR from the 'Tools' menu > 'TIG.Smart_offset'.
OR from the Context-menu > 'TIG.Smart_offset' .
OR from the [activated] Toolbar > 'TIG.Smart_offset'.

Which use the method:
  TIG::Smart_offsetter.dialog()

In the dialog you enter the required offset in current units, 
[or add a units suffix for non-current dimensions].
A +ve offset is applied outside of the face's outer_loop and a -ve offset is 
made inside the face's outer_loop.
It mimics the native offset-tool in most respects, other than removing 
many of the spurious twisted offset line/loops.
Curves/Arcs are offset as curves in the new offset outline.
ALL selected faces are offset by the distance specified.
The last used offset distance is remembered during that session.
It is one step undo-able for all offsets.
Do not use with the Outliner open and rolled-down, as this may cause splats!

Version:
1.0 20121221 First issue.
1.1 20121221 Made more robust.
1.2 20121221 Further robustness in enabling/suitable? [thanks to Jim!].
1.3 20121221 Removed 'suitable?' method, to suit MACs, more error traps etc.
1.4 20121222 Validation on applied to PCs, to avoid MAC toolbar glitch,
1.5 20121224 Coding improved for truer, tidier offsets.
1.6 20121225 Curves/Arcs are now offset as curves in the new offset outline.
1.7 20121227 Further glitches and convoluted offsets trapped.
1.8 20121228 Single welded curve loop offsets correctly. Fixed typo in weld.
1.9 20121230 Error in code when called within another script fixed.
2.0 20121231 Further refinements and glitch trapping.
2.1 20121231 Glitch trapping extended to curves.

Donations:
By PayPal.com to info @ revitrev.org
###
=end

require 'sketchup.rb'

module TIG

  module Smart_offsetter
		
	def self.dialog()
		model=Sketchup.active_model
		ss=model.selection
		faces=ss.grep(Sketchup::Face)
		unless faces[0]
			UI.messagebox("TIG.Smart_offset:\n\nPreSelect Face[s] !")
			return nil
		end
		@dist = 0.0.mm unless @dist
		prompts=["Offset [+/-ve=out/inside]: "]
		values=[@dist]
		pops=[""]
		title="TIG.Smart_offset"
		results=inputbox(prompts, values, pops, title)
		return nil unless results
		@dist, = results
		if @dist==0
			UI.messagebox("TIG.Smart_offset:\n\nOffset of ZERO does nothing !")
			self.dialog()
			return nil
		end
		tidy=false
		#ss.clear
		model.start_operation("TIG.Smart_offset")
		  faces.each{|face|
			Sketchup.status_text="Smart_offsetting..."
			TIG::Smart_offset.new(face, @dist, tidy)
			Sketchup.status_text="Done..."
		  }
		  Sketchup.status_text=""
		model.commit_operation
	end#def
	
	unless file_loaded?(__FILE__)
		dir=File.join(File.dirname(__FILE__), "TIG-Smart_offset")
		cmd=UI::Command.new('TIG.Smart_offset'){
			TIG::Smart_offsetter.dialog()
		}
		cmd.set_validation_proc{
			Sketchup.active_model.selection.grep(Sketchup::Face)[0] ? MF_ENABLED : MF_GRAYED
		} unless RUBY_PLATFORM =~ /darwin/
		cmd.tooltip='TIG.Smart_offset'
		cmd.status_bar_text='TIG.Smart_offset: Offsets Select Faces, Smartly...'
		UI.menu('Tools').add_item(cmd)
		cmd.small_icon=File.join(dir, 'TIG-Smart_offset.png')
		cmd.large_icon=File.join(dir, 'TIG-Smart_offset.png')
		toolbar=UI::Toolbar.new('TIG.Smart_offset')
		toolbar.add_item(cmd)
		toolbar.restore if toolbar.get_last_state.abs == 1 #TB_VISIBLE/NEVER
		UI.add_context_menu_handler{|menu|
		    menu.add_item(cmd) if Sketchup.active_model.selection.grep(Sketchup::Face)[0] #|| RUBY_PLATFORM =~ /darwin/
		}
	end#unless
	file_loaded(__FILE__)
	
  end#module

  class Smart_offset
  
	def initialize(face=nil, dist=nil, tidy=false)
      begin
		return nil unless face && face.is_a?(Sketchup::Face) && face.valid?
		return nil unless dist && ((dist.class==Fixnum || dist.class==Float || dist.class==Length) && dist!=0)
        model=Sketchup.active_model
        norm=face.normal
        mat=face.material
        bat=face.back_material
        lay=face.layer
        ents=face.parent.entities
        selected=false
        ss=Sketchup.active_model.selection
        selected=true if ss.to_a.include?(face)
        ###
        loop=face.outer_loop
		edges=[]
		curves=[]
		lines=[]
		loop.edges.each{|e|
			if e.curve
				curves << e.curve
				edges  << e
				lines << [e.line, e.start.position, e.end.position]
			else
				edges << e
				lines << [e.line, e.start.position, e.end.position]
			end
		}
		curves.uniq!
		verts=loop.vertices
		pts=[]
		### create array pts of offset points from face 
		verts.each_index{|i|
            pt=verts[i].position
            vec1=pt.vector_to(verts[i-(verts.length-1)].position).normalize
			vec2=pt.vector_to(verts[i-1].position).normalize
            ang=vec1.angle_between(vec2)/2.0
			vec3=(vec1+vec2).normalize
		    if vec1.parallel?(vec2)
              ang=90.degrees 
              tpa=pt.offset(vec1)
              tr=Geom::Transformation.rotation(pt, norm, ang)
              tpa.transform!(tr)
              vec3=pt.vector_to(tpa)
            end
			if vec3 && vec3.valid? && vec3.length>0
                vec3.length=0.5.mm ### v2.0 increased tolerance
                tpt=pt.offset(vec3)
                if dist > 0 ### ensure start at internal corner is extl.
                  if face.classify_point(tpt) == Sketchup::Face::PointInside
                    vec3.reverse!
                  end
                else ### < 0 should be intl.
                  if face.classify_point(tpt) != Sketchup::Face::PointInside
                    vec3.reverse!
                  end
                end
                vec3.length=(dist/Math::sin(ang)).abs
				pts << pt.offset(vec3)
			end#if
		}
		###
		dups=[]
		dup=pts.dup
		dup.each_with_index{|p, i|
			dus=[]
			pts.each_with_index{|pp, ii|
				next if ii == i
				dus << pp if pp == p
			}
			dups << dus
		}
		puds=[]
		dups.each{|dup| puds << dup[0] }
		pts=pts-puds
        ###
        unless pts[2]
          puts "#{face} can't offset !"
          p pts
          return nil
        end
        ### make set of nested groups to add edges
        gp=ents.add_group()
        gents=gp.entities
		### add other edges
		pts<<pts[0]
		gents.add_edges(pts)
		###
		#return
		### sort out curves
		if curves.length==1 && curves[0].edges.length==edges.length
			looped=true
		else
			looped=false
		end
		togos=[]
		tgps=[]
		curves.each{|c| #ss.clear;ss.add c.edges;UI.messagebox('?');
			cpts=[]
			c.vertices.each{|v|
				eds=v.edges
				eds.each{|e|eds.delete(e) unless edges.include?(e)}
				pt=v.position
				vec1=pt.vector_to(eds[0].other_vertex(v).position).normalize
				vec2=pt.vector_to(eds[1].other_vertex(v).position).normalize
				ang=vec1.angle_between(vec2)/2.0
				vec3=(vec1+vec2).normalize
				if vec1.parallel?(vec2)
				  ang=90.degrees 
				  tpa=pt.offset(vec1)
				  tr=Geom::Transformation.rotation(pt, norm, ang)
				  tpa.transform!(tr)
				  vec3=pt.vector_to(tpa)
				end
				if vec3 && vec3.valid? && vec3.length>0
					vec3.length=0.5.mm ### v2.1
					tpt=pt.offset(vec3)
					if dist > 0 ### ensure start at internal corner is extl.
					  if face.classify_point(tpt) == Sketchup::Face::PointInside
						vec3.reverse!
					  end
					else ### < 0 should be intl.
					  if face.classify_point(tpt) != Sketchup::Face::PointInside
						vec3.reverse!
					  end
					end
					vec3.length=(dist/Math::sin(ang)).abs
					pt.offset!(vec3)
				end#if
				if cpts[0]
					dup=false
					cpts.each{|p|
						if p==pt
							dup=true
							break
						end
					}
					cpts << pt unless dup
				else ### 1st pt
					cpts << pt
				end
			}
			next unless cpts[1]
			cpts << cpts[0] if looped ### one loop perimeter
			tgp=gents.add_group()
			tgp.entities.add_curve(cpts)
			togos << gents.add_edges(cpts)
			tgps << tgp
			### add spurious extensions to break
		   if dist < 0
			p=cpts[0]
			v=cpts[1].vector_to(cpts[0])
			rayt=model.raytest([p, v])
			if rayt && p.distance(rayt[0])<=dist.abs
				tgp.entities.add_curve(p, rayt[0])
				togos << gents.add_edges(cpts)
			end
			rayt=model.raytest([p, v.reverse])
			if rayt && p.distance(rayt[0])<=dist.abs
				tgp.entities.add_curve(p, rayt[0])
				togos << gents.add_edges(cpts)
			end
			p=cpts[-1]
			v=cpts[-2].vector_to(cpts[-1])
			rayt=model.raytest([p, v])
			if rayt && p.distance(rayt[0])<=dist.abs
				tgp.entities.add_curve(p, rayt[0])
				togos << gents.add_edges(cpts)
			end
			rayt=model.raytest([p, v.reverse])
			if rayt && p.distance(rayt[0])<=dist.abs
				tgp.entities.add_curve(p, rayt[0])
				togos << gents.add_edges(cpts)
			end
		   end#if
		}
		###
		### remove edges that will be curves
		togos.flatten!
		togos.uniq!
		gents.erase_entities(togos) if togos[0]
		### double tidy
		gedges=gents.grep(Sketchup::Edge)
		togos=[]
		gedges.each{|e|
			p0=e.start.position
			p1=e.end.position
			di=e.length
			tgps.each{|tgp|
				eds=tgp.entities.grep(Sketchup::Edge)
				eds.each{|ed|
					edi=ed.length
					next unless edi==di
					ep0=e.start.position
					ep1=e.end.position
					next unless (ep0==p0 && ep1==p1) || (ep0==p1 && ep1==p0)
					togos << e
				}
			}
		}
		togos.uniq!
		gents.erase_entities(togos) if togos[0]
		###
		### do splits
        tr=Geom::Transformation.new()
		gents.intersect_with(true, tr, gents, tr, true, gents.to_a)
        gents.intersect_with(true, tr, gents, tr, true, edges)
		###
		#return
		### tidy up
		if dist < 0 ### inner
			###
			#return
			### consolidate near vertices
			gedges=gents.grep(Sketchup::Edge)
			vs=[]
			gedges.each{|e|vs << e.vertices}
			vs.flatten!
			vs.uniq!
			tgp=gents.add_group()
			vs.each{|v|
				tgp.entities.add_line(v.position, v.position.offset(norm))
			}
			tgp.explode
			gedges=gents.grep(Sketchup::Edge)
			togos=[]
			gedges.each{|e|
				togos << e if e.line[1].parallel?(norm)
			}
			gents.erase_entities(togos) if togos[0]
			###
			#return
			###remove all too near
			gedges=gents.grep(Sketchup::Edge)
			togos=[]
			gedges.each{|e|
				done=[]
				e.vertices.each{|v|
					lines.each{|a|
						line=a[0]
						pt1=a[1]
						pt2=a[2]
						pv=v.position
						pp=pv.project_to_line(line)
						ppbetween=false
						###
						d1=pp.distance(pt1)+pp.distance(pt2)
						d2=pt1.distance(pt2)
						if d1 <= d2 || d1-d2 < 1e-10
						  ppbetween=true
						end
						###
						if pv.distance(pp) < dist.abs && ppbetween
							done << 1
						elsif pv.distance(pp) == dist.abs && ppbetween
							done << 0
						end
					}
					#p done if done[0]
					if done.length==2 && done.include?(1)
						togos << e
					elsif done.length>=4 && (done-[0]).length==done.length/2
						togos << e
					end
				}
			}
			togos.uniq!
			#ss.clear;ss.add togos
			#return
			gents.erase_entities(togos) if togos[0]
			### find loose ends
			gedges=gents.grep(Sketchup::Edge)
			ledges=[]
			gedges.each{|e|ledges << e if e.start.edges.length==1 || e.end.edges.length==1}
			nedges=[]
			ledges.each{|e|
				vs=e.vertices
				vs.each{|v|
					next if v.edges[1]
					pv=v.position
					ve=pv.vector_to(e.other_vertex(v).position).reverse
					rayt=model.raytest([pv, ve])
					if rayt && rayt[1].include?(gp)
						nedges << gents.add_line(pv, rayt[0])
					end
				}
			}
			gents.intersect_with(true, tr, gents, tr, true, gents.to_a)
			gedges=gents.grep(Sketchup::Edge)
			togos=[]
			gedges.each{|e|
				togos << e if e.start.edges.length==1 || e.end.edges.length==1
			} unless tgps[0]
			togos.uniq!
			#ss.clear;ss.add togos
			#return
			gents.erase_entities(togos) if togos[0]
			###
			#return
			### add short splitters at curves etc
			gedges=gents.grep(Sketchup::Edge)
			ps=[]
			gedges.each{|e|
				e.vertices.each{|v|
					pv=v.position
					rayt = model.raytest([pv, e.line[1]])
					if rayt && rayt[1].include?(gp) && rayt[1][-1].curve
						ps << [pv, rayt[0]]
					end
					rayt = model.raytest([pv, e.line[1].reverse])
					if rayt && rayt[1].include?(gp) && rayt[1][-1].curve
						ps << [pv, rayt[0]]
					end
				}
			}
			###
			#return
			###
			tgps.each{|tgp|
				gedges=tgp.entities.grep(Sketchup::Edge)
				e0=gedges[0].curve.first_edge
				p=e0.start.position
				v=e0.line[1]
				rayt=model.raytest([p, v])
				if rayt && rayt[1].include?(gp) && ! e0.curve.edges.include?(rayt[1][-1])
					gents.add_line(p, rayt[0])
				end
				rayt=model.raytest([p, v.reverse])
				if rayt && rayt[1].include?(gp) &&  e0.curve.edges.include?(rayt[1][-1])
					gents.add_line(p, rayt[0])
				end
				p=e0.end.position
				v=e0.line[1]
				rayt=model.raytest([p, v])
				if rayt && rayt[1].include?(gp) && ! e0.curve.edges.include?(rayt[1][-1])
					gents.add_line(p, rayt[0])
				end
				rayt=model.raytest([p, v.reverse])
				if rayt && rayt[1].include?(gp) &&  e0.curve.edges.include?(rayt[1][-1])
					gents.add_line(p, rayt[0])
				end
			}
			###
			#return
			### ?
			tgp=gents.add_group()
			ps.each{|a|tgp.entities.add_line(a)}
			tgp.explode
			###
			#return
			### intersect
			gents.intersect_with(true, tr, gents, tr, true, gents.to_a)
			gents.intersect_with(true, tr, gents, tr, true, edges)
			###
			#return
			### weld tgps curves into one curve each
			tgps.each{|tgp|
				eds=tgp.entities.to_a
				verts=[]
				newVerts=[]
				startEdge=startVert=nil
				#GET EDGES & VERTICES
				next if eds.length < 2
				ents=eds[0].parent.entities
				eds.each{|e|verts << e.vertices}
				verts.flatten!
				#FIND AN END VERTEX
				vertsShort=[]
				vertsLong=[]
				verts.each{|v|
					if vertsLong.include?(v)
						vertsShort << v
					else
						vertsLong << v
					end
				}
				if (startVert=(vertsLong-vertsShort)[0])==nil
					startVert=vertsLong[0]
					closed=true
					startEdge=startVert.edges[0]
				else
					closed=false
					startEdge=(eds & startVert.edges)[0]
				end
				#SORT VERTICES, LIMITING TO THOSE IN THE SELECTION SET
				if startVert==startEdge.start
					newVerts=[startVert]
					counter=0
					while newVerts.length < verts.length
						eds.each{|edge|
							if edge.end==newVerts[-1]
								newVerts << edge.start
							elsif edge.start==newVerts[-1]
								newVerts << edge.end
							end
						}
						counter+=1
						if counter > verts.length
							newVerts.reverse!
							reversed=true
						end
					end
				else
					newVerts=[startVert]
					counter=0
					while newVerts.length < verts.length
						eds.each{|edge|
							if edge.end==newVerts[-1]
								newVerts << edge.start
							elsif edge.start==newVerts[-1]
								newVerts << edge.end
							end
						}
						counter+=1
						if counter > verts.length
							newVerts.reverse!
							reversed=true
						end
					end
				end
				newVerts.uniq!
				newnewVerts=[]
				newVerts.each_with_index{|v, i|
				  break if i==newVerts.length-1
				  newnewVerts << v
				  edged=false
				  eds.each{|e|
					if e.start.position==v.position and e.end.position==newVerts[1+i].position
					  newnewVerts << newVerts[1+i]
					  edged=true
					  break
					elsif e.end.position==v.position and e.start.position==newVerts[1+i].position
					  newnewVerts << newVerts[1+i]
					  edged=true
					  break
					end
				  }
				  break unless edged
				}
				next unless newnewVerts[1]
				newVerts=newnewVerts
				newVerts.reverse! if reversed
				newVerts << newVerts[0] if closed
				ttgp=tgp.entities.add_group()
				ttgp.entities.add_curve(newVerts)
				tgp.entities.erase_entities(eds)
				ttgp.explode
			}
			###
			#return
			### add curves back
			tgps.each{|tgp|
				next unless tgp.valid?
				es=tgp.entities.grep(Sketchup::Edge)
				cs=[]
				es.each{|e|cs << e.curve if e.curve}
				cs.uniq!
				cs.each{|c|
					vs=c.vertices
					gents.add_curve(vs)
				}
				tgp.erase!
			}
			###
			#return
			### intersect
			gents.intersect_with(true, tr, gents, tr, true, gents.to_a)
			gents.intersect_with(true, tr, gents, tr, true, edges)
			###
			#return
			### force into separate faces
			gedges=gents.grep(Sketchup::Edge)
			tgp=gents.add_group()
			vs=[]
			gedges.each{|e|vs << e.vertices}
			vs.flatten!
			vs.uniq!
			vs.each{|v|
				curved=false
				v.edges.each{|e|
					if e.curve
						curved=true
						break
					end
				}
				next if curved && v.edges[1] ### catch ends
				tgp.entities.add_line(v.position, v.position.offset(norm))
			}
			tgp.explode
			###
			#return
			###
			gedges=gents.grep(Sketchup::Edge)
			togos=[]
			gedges.each{|e|togos << e if e.line[1].parallel?(norm)}
			gents.erase_entities(togos) if togos[0]
			###
			#return
			###
			gedges=gents.grep(Sketchup::Edge)
			togos=[]
			gedges.each{|e|
				e.vertices.each{|v|
					done=false
					lines.each{|a|
						line=a[0]
						pt1=a[1]
						pt2=a[2]
						pv=v.position
						pp=pv.project_to_line(line)
						ppbetween=false
						###
						d1=pp.distance(pt1)+pp.distance(pt2)
						d2=pt1.distance(pt2)
						if d1 <= d2 || d1-d2 < 1e-10
						  ppbetween=true
						end
						###
						if pv.distance(pp) < dist.abs && ppbetween
							togos << e
							done=true
							break
						end
					}
					break if done
				}
			}
			togos.uniq!
			gents.erase_entities(togos) if togos[0]
			###
			#return
			###
			gedges=gents.grep(Sketchup::Edge)
			togos=[]
			gedges.each{|e|
				e.vertices.each{|v|
					cp=face.classify_point(v.position)
					unless cp==Sketchup::Face::PointInside || cp==Sketchup::Face::PointOnVertex || cp==Sketchup::Face::PointOnEdge
						togos << e
						break
					end
				}
			}
			togos.uniq!
			gents.erase_entities(togos) if togos[0]
			#return
			###
			gedges=gents.grep(Sketchup::Edge)
			gedges.each{|e|e.find_faces}
			togos=[]
			gedges.each{|e|togos << e unless e.faces.length==1} ### del coplanar
			gents.erase_entities(togos) if togos[0]
			###
			#return
			### reweld
			gedges=gents.grep(Sketchup::Edge)
			cs=[]
			gedges.each{|e|cs << e.curve if e.curve && e.curve.edges.length==1}
			cs.uniq!
			cs.each{|c|
				next if c.edges[1]
				ed=c.edges[0]
				vs=ed.vertices
				vx=nil
				vs.each{|v|
					(v.edges-[ed]).each{|ee|
						next unless ee.line[1] && ee.line[1].length!=0
						if ee.line[1].parallel?(ed.line[1])
							vx=v
							break
						end
						ang=ee.line[1].angle_between(ed.line[1])
						#p ang.radians
						if ang<2.degrees || ang>178.degrees
							vx=v
							break
						end
					}
					break if vx
				}
				next unless vx
				vxx=(vs-[vx])[0]
				vect=vx.position.vector_to(vxx.position)
				tt=Geom::Transformation.translation(vect)
				gents.transform_entities(tt, vx)
			}
			###
			### match faces
			gfaces=gents.grep(Sketchup::Face)
			gfaces.each{|f|
				f.layer=face.layer
				f.material=face.material
				f.back_material=face.back_material
				f.reverse! unless f.normal==norm
			}
			###
			gp.explode
			###
		else ### it's an outer offset; d > 0 ######################
			###
			### add splitter at curves
			gedges=gents.grep(Sketchup::Edge)
			vs=[]
			gedges.each{|e|vs << e.vertices}
			ps=[]
			gedges.each{|e|break
				e.vertices.each{|v|
					p=v.position
					rayt=model.raytest([p, e.line[1]])
					if rayt && rayt[1].include?(gp) && (e.curve && ! e.curve.edges.include?(rayt[1][-1]))
						ps << [p, rayt[0]]
					end
					rayt=model.raytest([p, e.line[1].reverse]) && (e.curve && ! e.curve.edges.include?(rayt[1][-1]))
					if rayt && rayt[1].include?(gp)
						ps << [p, rayt[0]]
					end
				}
			}
			tgp=gents.add_group()
			ps.each{|a|tgp.entities.add_line(a)}
			#return
			tgp.explode
			#return
			### add curves
			tgps.each{|tgp|tgp.explode if tgp.valid?}
			###
			#return
			### intersect
			gents.intersect_with(true, tr, gents, tr, true, gents.to_a)
			gents.intersect_with(true, tr, gents, tr, true, edges)
			###
			tr=Geom::Transformation.new()
			gents.intersect_with(true, tr, gents, tr, true, gents.to_a)
			###
			gedges=gents.grep(Sketchup::Edge)
			vs=[]
			gedges.each{|e|vs << e.vertices}
			vs.flatten!
			vs.uniq!
			tgp=gents.add_group()
			vs.each{|v|#next unless v.edges[2]
				curved=false
				v.edges.each{|e|
					if e.curve
						curved=true
						break
					end
				}
				next if curved && v.edges[1] ### catch ends
				tgp.entities.add_line(v.position, v.position.offset(norm))
			}
			tgp.explode
			gedges=gents.grep(Sketchup::Edge)
			gedges.each{|e|e.find_faces}
			togos=[]
			gedges.each{|e|togos << e if e.line[1].parallel?(norm)}
			gents.erase_entities(togos) if togos[0]
			###
			gedges=gents.grep(Sketchup::Edge)
			togos=[]
			gedges.each{|e|togos << e if e.faces[1] || e.faces.length==0}### coplanar
			gents.erase_entities(togos) if togos[0]
			###
			gedges=gents.grep(Sketchup::Edge)
			gedges.each{|e|e.find_faces}
			###
			togos=[]
			gedges.each{|e|togos << e unless e.faces[0]}
			gents.erase_entities(togos) if togos[0]
			###
			if model.active_entities==ents
				tgp=ents.add_group(edges)
			else
				tgp=ents.add_group()
				tgp.entities.add_edges(verts+[verts[0]])
			end
			tgpp=gents.add_instance(tgp.entities.parent, tgp.transformation)
			tgp.erase!
			tr=Geom::Transformation.new()
			tgpp.entities.intersect_with(true, tr, tgpp.entities, tr, true, gents.to_a)
			tgpp.explode
			###
			gfaces=gents.grep(Sketchup::Face)
			togos=[]
			gfaces.each{|f|togos << f if f.loops.length==1}
			gents.erase_entities(togos) if togos[0]
			###
			gedges=gents.grep(Sketchup::Edge)
			togos=[]
			gedges.each{|e|togos << e unless e.faces[0]}
			gents.erase_entities(togos) if togos[0]
			### match faces
			gfaces=gents.grep(Sketchup::Face)
			gfaces.each{|f|
				f.layer=face.layer
				f.material=face.material
				f.back_material=face.back_material
				f.reverse! unless f.normal==norm
			}
			###
			gp.explode
			###
		end
		###
        ss.add(face) if selected
		return gfaces
		###
      rescue => e
	    puts "Error: #{e.message}"
		puts "Stacktrace: #{e.backtrace}"
	    return nil
	  end
      
	end#def
	
  end#Smart_offset
  
end#TIG
