=begin
###
Copyright 2013-2014, TIG (c)
###
Permission to use, copy, modify, and distribute this software for 
any purpose and without fee is hereby granted, provided the above
copyright notice appear 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.
###
=end
###

module TIG
###
S5_MENU="Plugins" ### could be changed to 'Tools' etc.
S5_SUBMENU=true   ### OR =false to leave it as 4 separate menu entries.
###

### make deBabelizer
def self.db(string)
    dir=File.dirname(__FILE__)
    toolname="TIG-Slicer"
    locale=Sketchup.get_locale.upcase
    path=File.join(dir, toolname+locale+".lingvo")
    unless File.exist?(path)
        return string
    else
        deBabelizer(string, path)
    end#if
end#def db

### deBabelizer CONSTANTS
S5=db("Slicer5")
ST=db("Slicing tool")
SR=db("Slotter")
SLT=db("Slotting tool")
SA=db("Audit")
SAT=db("Audit tool")
SX=db("Exporter")
SXT=db("Exporter tool")
V8=db("This Tool for Version 8+ ONLY !")
SAV=db("Save this SKP before using this tool")
NOS=db("NO Selection !")
SONE=db("Selection MUST be ONE Group or Component-Instance !")
SOGC=db("Selection is NOT a Group or Component-Instance !")
SOMS=db("The Selected Object is NOT a 'Manifold' Solid !")
PARA=db("Parameters")
Y=db("Yes")
N=db("No")
AXIS=db("Axis")
SPAC=db("Spacing")
THCK=db("Thickness")
INST=db("Inset at Start")
INEN=db("Inset at End")
#
CENTN=db("Centralization")
SLICE=db("Slice")
GAP=db("Gap")
#
AREF=db("Add References ?")
TXTH=db("Text Height")
FLATQ=db("Flatten ?")
SLCS=db("Slices")
THCK0=db("Thickness CAN'T be < 0 !")
USE0=db("Using +ve value")
THCKS=db("Thickness CANNOT be > Spacing !")
TEQS=db("Thickness = Spacing")
INST0=db("Inset at Start CAN'T be < 0 !")
USED=db("Using default")
INEN0=db("Inset at End CAN'T be < 0 !")
ADDO=db("Add the Outlines of Adjacent Slices ?")
ANG=db("Angle")
SLOT=db("Slot ?")
TOL=db("Tolerance")
OCUT=db("Overcut")
SPACS=db("Spaces")
SLING=db("Slicing")
STING=db("Slotting")
FTING=db("Fattening")
ORING=db("Originating")
OLING=db("Outlining")
RFING=db("Referencing")
MKING=db("Making Components")
FLING=db("Flattening")
DONE=db("Done.")
EXPR=db("Exporter")
SOFLAT=db("Selection MUST be ONE 'FLAT-NNNN' Group !")
SNOTF=db("Selection is NOT a 'FLAT-NNNN' Group !")
QPNG=db("Do you want to Export these Slices as PNG Image Files ?")
QDXF=db("Do you want to Export these Slices as DXF Files ?")
PNGS=db("PNG Image Files into")
DXFS=db("DXF Files Completed.")
DXFX=db("DXF Files Exported into")
SSG=db("Selection MUST be ONE 'SLICES-NNNN' Group !")
SNOTS=db("Selection is NOT a 'SLICES-NNNN' Group !")
SGRP=db("Selected Group")
NOMA=db("has NO Matching")
GRP=db("Group")
SONOS=db("so NO Slotting is Possible !")
ZERO=db("has ZERO Thickness Slice[s]")
SMING=db("Slimming")
THING=db("Thickening")
INING=db("Intersecting")
VALID=db("VALIDITY CHECK")
NONER=db("NON-INSTANCES: ERASED")
OK=db("OK")
TREFT=db("TEXT-REF-TAGS")
TMISS=db("'TEXT' MISSING... ADDED")
RATMU=db("'REF ATTRIBUTE' MISSING")
TXTOK=db("'TEXT' REF OK")
TXREF=db("'TEXT' REF")
UPDATED=db("UPDATED")
FLAT=db("FLAT")
NONE=db("NONE")
INSTS=db("INSTANCES")
MISMATCH=db("MISMATCH")
ADDED=db("ADDED")
TFLAT=db("TO")
REMOVED=db("REMOVED")
FROM=db("FROM")
INSMA=db("INSTANCES MATCH")
AREP=db("Audit_Report")
ARNOT=db("Audit Report NOT written!")
ARPRO=db("It's probably open in another application, close it and try again")
NOCHA=db("NO Changes have been made")
SHOWS=db("Shows ERRORS - inspect before retrying")
TTOG=db("Toolbar Toggle")
###

### menu ----------------------------------
unless file_loaded?(File.basename(__FILE__))
  cmd=UI::Command.new(S5){TIG::Slicer.new()}
  cmd.tooltip=S5
  cmd.status_bar_text=S5+": "+ST+"..."
  cmd.small_icon="Slicer5-16.png"
  cmd.large_icon="Slicer5-24.png"
  ###
  cmds=UI::Command.new(S5+" "+SR){TIG::Slicer.slotter()}
  cmds.tooltip=S5+" "+SR
  cmds.status_bar_text=S5+": "+SLT+"..."
  cmds.small_icon="Slicer5-slotter-16.png"
  cmds.large_icon="Slicer5-slotter-24.png"
  ###
  cmda=UI::Command.new(S5+" "+SA){TIG::Slicer.audit(true)}
  cmda.tooltip=S5+" "+SA
  cmda.status_bar_text=S5+": "+SAT+"..."
  cmda.small_icon="Slicer5-audit-16.png"
  cmda.large_icon="Slicer5-audit-24.png"
  ###
  cmdx=UI::Command.new(S5+" "+SX){TIG::Slicer.export()}
  cmdx.tooltip=S5+" "+SX
  cmdx.status_bar_text=S5+": "+SXT+"..."
  cmdx.small_icon="Slicer5-exporter-16.png"
  cmdx.large_icon="Slicer5-exporter-24.png"
  ###
  toolbar=UI::Toolbar.new(S5)
  ###
if S5_SUBMENU #############################################################
  submenu=UI.menu(S5_MENU).add_submenu(S5+"...")
  submenu.add_item(cmd)
  submenu.add_item(cmds)
  submenu.add_item(cmda)
  submenu.add_item(cmdx)
  submenu.add_item(TTOG){toolbar.visible? ? toolbar.hide : toolbar.restore}
else
  UI.menu(S5_MENU).add_item(cmd)
  UI.menu(S5_MENU).add_item(cmds)
  UI.menu(S5_MENU).add_item(cmda)
  UI.menu(S5_MENU).add_item(cmdx)
  UI.menu(S5_MENU).add_item(TTOG){toolbar.visible? ? toolbar.hide : toolbar.restore}
end#if SUBMENU #########################################################
  ###
  toolbar.add_item(cmd)
  toolbar.add_item(cmds)
  toolbar.add_item(cmda)
  toolbar.add_item(cmdx)
  toolbar.restore if toolbar.get_last_state.abs==1 #TB_VISIBLE/NEVER
end#if
file_loaded(File.basename(__FILE__)) ###################################

##################======================================================
class Slicer

###---------------------------------------
   ###
   @@axis=nil
   @@spacing1=nil
   @@thickness1=nil
   @@inset_start1=nil
   @@inset_end1=nil
   ###
   @@centn1=nil
   @@centn2=nil
   ###
   @@ref=nil
   @@tht=nil
   @@flatten=nil
   ###
   @@spacing2=nil
   @@thickness2=nil
   @@inset_start2=nil
   @@inset_end2=nil
   ###
   @@slot=nil
   @@tol=nil
   @@bit=nil
   ###
   @@spaces1=0.0
   @@spaces2=0.0
   ###
   @@angle=nil
   ###
###---------------------------------------

def initialize()
  ###
  @x0=0.mm
  ###
  if Sketchup.version.split(".")[0].to_i < 8
    UI.messagebox(S5+":\n\n"+V8)
	return nil
  end#if
  @model=Sketchup.active_model
  if @model.path.empty?
    UI.messagebox(S5+":\n\n"+SAV+"...")
    return nil
  end
  @model.active_layer=nil
  @ss=@model.selection
  if @ss.empty?
    UI.messagebox(S5+":\n\n"+NOS)
    return nil
  end#if
  if @ss[1]
    UI.messagebox(S5+":\n\n"+SONE)
    return nil
  end#if
  unless @ss[0].is_a?(Sketchup::Group) || @ss[0].is_a?(Sketchup::ComponentInstance)
    UI.messagebox(S5+":\n\n"+SOCG)
    return nil
  end#if
  @selected=@ss[0]
  ### check if 'manifold'
  unless @selected.manifold?
    UI.messagebox(S5+":\n\n"+SOMS)
    return nil
  end#if
  ### get bb etc
  ###
  @trans=@selected.transformation
  @bb=@selected.bounds
  @max=@bb.max
  @min=@bb.min
  @maxZ=@max.z-@min.z
  @maxY=@max.y-@min.y
  @maxX=@max.x-@min.x
  @offZ=@maxZ/2.0
  @offY=@maxY/2.0
  @offX=@maxX/2.0
  @cent=@bb.center
  @maxZC=@cent.clone
  @maxZC.z=@max.z
  @maxYC=@cent.clone
  @maxYC.y=@max.y
  @maxXC=@cent.clone
  @maxXC.x=@max.x
  @diag=@bb.diagonal
### set up @@spacing1, @@thickness1 etc in def dialog()
### show VCB and status info
  Sketchup::set_status_text("", SB_VCB_LABEL)
  Sketchup::set_status_text("", SB_VCB_VALUE)
  @msg=S5+": "
  Sketchup.status_text=@msg+PARA+"..."
  ### get settings
  return nil unless self.dialog()
  ### run
  self.run()
  ###
end#initialize
##############

############
def dialog()
  if @model.options["UnitsOptions"]["LengthUnit"]<=1 ### it's '/"
    xspacing1=2.0.inch
    xthickness1=0.125.inch
    xinset_start1=@x0
    xinset_end1=@x0
	xtht=0.5.inch
  else ##mmm
    xspacing1=50.mm
    xthickness1=3.mm
    xinset_start1=@x0
    xinset_end1=@x0
	xtht=12.mm
  end
  ###
  axes="X|Y|Z|XY|XZ|YZ|A|AZ|R|RZ"
  centn="#{SLICE}|#{GAP}"
  refs=Y+"|"+N
  flatten=Y+"|"+N
  ###
  prompts=[AXIS+": ", SPAC+": ", THCK+": ", INST+": ", INEN+": ", CENTN+": ", AREF+" ", TXTH+": ", FLATQ+" "]
  ###
  title=@msg+PARA
  ###
  @@axis="Z" unless @@axis
  @@spacing1=xspacing1 unless @@spacing1
  @@thickness1=xthickness1 unless @@thickness1
  @@inset_start1=xinset_start1 unless @@inset_start1
  @@inset_end1=xinset_end1 unless @@inset_end1
  @@centn1=SLICE unless @@centn1
  @@ref=Y unless @@ref
  @@tht=xtht unless @@tht
  @@flatten=Y unless @@flatten
  ###
  values=[@@axis, @@spacing1, @@thickness1, @@inset_start1, @@inset_end1, @@centn1, @@ref, @@tht, @@flatten] ###
  pops=[axes, "", "", "", "", centn, refs, "", flatten] ###
  results=inputbox(prompts, values, pops, title)
  return nil unless results
  @@axis, @@spacing1, @@thickness1, @@inset_start1, @@inset_end1, @@centn1, @@ref, @@tht, @@flatten = results
  ###
  @outline=false
  self.dialogO()
  ###
### sort out slice 2/slots etc
part2=false
if @@axis=~/^[XY][YZ]$/ ### dual-axis
  return nil unless self.dialog1()
  part2=true
elsif @@axis=="AZ"
  return nil unless self.dialogA()
  return nil unless self.dialog1()
  part2=true
elsif @@axis=="A"
  return nil unless self.dialogA()
elsif @@axis=="R"
  @@spacing1=@x0
  @@inset_start1=@x0
  @@inset_end1=@x0
elsif @@axis=="RZ"
  @@spacing1=@x0
  @@spacing2=xspacing1 unless @@spacing2
  @@inset_start1=@x0
  @@inset_end1=@x0
  @@inset_start2=@x0 unless @@inset_start2
  @@inset_end2=@x0 unless  @@inset_end2
  return nil unless self.dialog1()
  part2=true
end
###
if @@thickness1<0
   @@thickness1= -@@thickness1
   UI.messagebox(S5+":\n\n"+THCK0+"\n"+USEP+" = #{@@thickness1}")
end#if
if @@thickness1>@@spacing1 && @@spacing1>0
   if xthickness1<@@spacing1
      @@thickness1=xthickness1 
   else
      @@thickness1=@@spacing1
   end#if
   UI.messagebox(S5+":\n\n"+THCKS+"\n"+TEQS+" = #{@@thickness1}")
end#if
if @@inset_start1<0
   @@inset_start1=xinset_start1
   UI.messagebox(S5+":\n\n"+INST0+"\n"+USED+" = #{@@inset_start1}")
end#if
if @@inset_end1<0
   @@inset_end1=xinset_end1
   UI.messagebox(S5+":\n\n"+INEN0+"\n"+USED+" = #{@@inset_end1}")
end#if
if @@tht<=0
  @@tht=xtht
end
### sort out by spaces
if @@spacing1<=0
   @@spacing1=@x0 
end#if
### spacing
if @@spacing1==0 || (part2 && @@spacing2==0)
  return nil unless self.dialog2()
end
###
  return true
  ###
end#dialog
########## #############################################################
def dialogO()
  return nil unless @@axis=~/^[XYZA]$/
  return nil unless @@thickness1==@@spacing1
  UI.beep
  ans=UI.messagebox(S5+":\n\n"+ADDO,MB_YESNO,"")
  if ans==6
	@outline=true
  else
    @outline=false
  end
end
########## #############################################################
def dialogA()
  ###
  prompts=[ANG+"° +/-ve [Z]: "]
  title=@msg+ANG+"..."
  Sketchup.status_text=title
  @@angle=30.0 unless @@angle
  values=[@@angle]
  pops=[""]
  results=inputbox(prompts, values, pops, title)
  return nil unless results
  @@angle = results[0]
  return true
  ###
end#dialogA
def dialog1()
  ###
    xspacing2=@@spacing1
    xthickness2=@@thickness1
    xinset_start2=@@inset_start1
    xinset_end2=@@inset_end1
	xtol=@x0
	xbit=@x0
  ###
  slots=Y+"|"+N
  centn="#{SLICE}|#{GAP}"
  ###
  prompts=[SPAC+": ", THCK+": ", INST+": ", INEN+": ", CENTN+": ", SLOT+" ", TOL+": ", OCUT+": "]
  ###
  title=@msg+"'"+(@@axis.split("")[1])+"' "+SLCS
  Sketchup.status_text=title+"..."
  ###
  @@spacing2=xspacing2 unless @@spacing2
  @@thickness2=xthickness2 unless @@thickness2
  @@inset_start2=xinset_start2 unless @@inset_start2
  @@inset_end2=xinset_end2 unless @@inset_end2
  @@centn2=SLICE unless @@centn2
  @@slot=Y unless @@slot
  @@slot=N if @@thickness1==0
  @@tol=xtol unless @@tol
  @@bit=xbit unless @@bit
  values=[@@spacing2, @@thickness2, @@inset_start2, @@inset_end2, @@centn2, @@slot, @@tol, @@bit] ###
  pops=["", "", "", "", centn, slots, "", ""] ###
  results=inputbox(prompts, values, pops, title)
  return nil unless results
  @@spacing2, @@thickness2, @@inset_start2, @@inset_end2, @@centn2, @@slot, @@tol, @@bit = results
  ###
  if @@spacing2<=0
    @@spacing2=@x0
  end
  ###
  if @@thickness2<0
    @@thickness2= -@@thickness2
    UI.messagebox(S5+": 2\n\n"+THCK0+"\n"+USE0+" = #{@@thickness2}")
  end#if
  if @@thickness2>@@spacing2 && @@spacing2>0
    if xthickness12<@@spacing2
      @@thickness2=xthickness2 
    else
      @@thickness2=@@spacing2
    end#if
   UI.messagebox(S5+": 2\n\n"+THCKS+"\n"+TEQS+" = #{@@thickness2}")
  end#if
  if @@inset_start2<0
     @@inset_start2=xinset_start2
     UI.messagebox(S5+": 2\n\n"+INST0+"\n"+USED+" = #{@@inset_start2}")
  end#if
  if @@inset_end2<0
    @@inset_end2=xinset_end2
    UI.messagebox(S5+": 2\n\n"+INEN0+"\n"+USED+" = #{@@inset_end2}")
  end#if
  if @@slot==N
    @@tol=xtol
	@@bit=xbit
  end
  ###
  return true
  ###
end#dialog1
########### ############################################################
def dialog2()
  ###
  Sketchup.status_text=@msg+SPACS+"..."
  ###
  @@spaces1=3 if @@axis=~/^[R]/ && @@spaces1<1
  @@spaces1=1 if @@spaces1<1
  @@spaces2=1 if @@spaces2<1
  ###
  prompt2=nil
  case @@axis
    when "Z"
      prompt1=SPACS+" [Z "+@maxZ.to_l.to_s+"]: "
	  prompts=[prompt1]
	  values=[@@spaces1]
    when "Y"
      prompt1=SPACS+" [Y "+@maxY.to_l.to_s+"]: "
	  prompts=[prompt1]
	  values=[@@spaces1]
    when "X"
      prompt1=SPACS+" [X "+@maxX.to_l.to_s+"]: "
	  prompts=[prompt1]
	  values=[@@spaces1]
    when "XY"
      prompt1=SPACS+" [X "+@maxX.to_l.to_s+"]: "
      prompt2=SPACS+" [Y "+@maxY.to_l.to_s+"]: "
	  if @@spacing1==0 && @@spacing2==0
	    prompts=[prompt1,prompt2]
	    values=[@@spaces1, @@spaces2]
	  elsif @@spacing1==0
	    prompts=[prompt1]
	    values=[@@spaces1]
	  elsif @@spacing2==0
	    prompts=[prompt2]
	    values=[@@spaces2]
	  end
    when "XZ"
      prompt1=SPACS+" [X "+@maxX.to_l.to_s+"]: "
      prompt2=SPACS+" [Z "+@maxZ.to_l.to_s+"]: "
	  if @@spacing1==0 && @@spacing2==0
	    prompts=[prompt1,prompt2]
	    values=[@@spaces1, @@spaces2]
	  elsif @@spacing1==0
	    prompts=[prompt1]
	    values=[@@spaces1]
	  elsif @@spacing2==0
	    prompts=[prompt2]
	    values=[@@spaces2]
	  end
    when "YZ"
      prompt1=SPACS+" [Y "+@maxY.to_l.to_s+"]: "
      prompt2=SPACS+" [Z "+@maxZ.to_l.to_s+"]: "
	  if @@spacing1==0 && @@spacing2==0
	    prompts=[prompt1,prompt2]
	    values=[@@spaces1, @@spaces2]
	  elsif @@spacing1==0
	    prompts=[prompt1]
	    values=[@@spaces1]
	  elsif @@spacing2==0
	    prompts=[prompt2]
	    values=[@@spaces2]
	  end
	when "A"
	  prompt1=SPACS+" [A="+@@angle.to_s+"° "+@maxX.to_l.to_s+"]: "
	  prompts=[prompt1]
	  values=[@@spaces1]
	when "AZ"
	  prompt1=SPACS+" [A="+@@angle.to_s+"° "+@maxX.to_l.to_s+"]: "
	  prompt2=SPACS+" [Z "+@maxZ.to_l.to_s+"]: "
	  if @@spacing1==0 && @@spacing2==0
	    prompts=[prompt1,prompt2]
	    values=[@@spaces1, @@spaces2]
	  elsif @@spacing1==0
	    prompts=[prompt1]
	    values=[@@spaces1]
	  elsif @@spacing2==0
	    prompts=[prompt2]
	    values=[@@spaces2]
	  end
	when "R"
	  ### Test ORIGIN to selected and set limits, vectors, angle etc
	  min=@bb.min
	  min.z=0
	  max=@bb.max
	  max.z=0
	  minx=min.clone
	  minx.y=max.y
	  maxn=max.clone
	  maxn.y=min.y
	  ###
	  if @bb.contains?(ORIGIN)
	    ### test if it's internal OR on an edge OR on a corner
		if min.x==0 ### corner OR edge
		  if min.y==0 ### BL corner
			@svec1=Y_AXIS.clone
			@svec2=X_AXIS.clone
		    @sangle=90.degrees
		  elsif minx.y==0 ### TL
			@svec1=X_AXIS.clone
			@svec2=Y_AXIS.clone.reverse
		    @sangle=90.degrees
		  else ### on L edge
			@svec1=Y_AXIS.clone
			@svec2=Y_AXIS.clone.reverse
		    @sangle=180.degrees
		  end
		elsif max.x==0 ### corner OR edge
		  if maxn.y==0 ### BR corner
			@svec1=X_AXIS.clone.reverse
			@svec2=Y_AXIS.clone
		    @sangle=90.degrees
	      elsif max.y==0 ### TR
			@svec1=Y_AXIS.clone.reverse
			@svec2=X_AXIS.clone.reverse
		    @sangle=90.degrees
		  else ### R edge
			@svec1=Y_AXIS.clone.reverse
			@svec2=Y_AXIS.clone
		    @sangle=180.degrees
		  end 
		elsif min.y==0 ### B edge
		  @svec1=X_AXIS.clone.reverse
		  @svec2=X_AXIS.clone
		  @sangle=180.degrees
		elsif max.y==0 ### T edge
		  @svec1=X_AXIS.clone
		  @svec2=X_AXIS.clone.reverse
		  @sangle=180.degrees
		else ### wholly within bb
		  @svec1=X_AXIS.clone
		  @svec2=X_AXIS.clone.reverse
		  @sangle=360.degrees
		end
		@outsideBB=false
		@sdist=@diag
		###
	  else ### ORIGIN is outside bb
	    ###
		@outsideBB=true
		@sdist=@diag+ORIGIN.distance(@bb.center)
	    ### check which quadrant OR if straddles an axis
		if min.x>0 && min.y>0 ### TR
		  @min000=minx
		  @max000=maxn
    	elsif min.x==0 && min.y>0 ### TR on Y axis
		  @min000=min
		  @max000=maxn
    	elsif min.y==0 && min.x>0 ### TR on X axis
		  @min000=minx
		  @max000=min
		elsif min.x>0 && max.y<0 ### BR
		  @min000=max
		  @max000=min
		elsif min.x==0 && max.y<0 ### BR on Y axis
		  @min000=minx
		  @max000=max
		elsif max.y==0 && min.x>0 ### BR on X axis
		  @min000=minx
		  @max000=min
		elsif max.x<0 && max.y<0 ### BL
		  @min000=maxn
		  @max000=minx
		elsif max.x==0 && max.y<0 ### BL on Y axis
		  @min000=minx
		  @max000=max
		elsif max.y==0 && max.x<0 ### BL on X axis
		  @min000=maxn
		  @max000=max
		elsif min.y>0 && max.x<0 ### TL
		  @min000=min
		  @max000=max
		elsif min.y>0 && max.x==0 ### TL on Y axis
		  @min000=min
		  @max000=maxn
		elsif min.y==0 && max.y<0 ### TL on X axis
		  @min000=maxn
		  @max000=max
		elsif min.x<0 && maxn.x>0 && min.y>0 ### on Y >0
		  @min000=min
		  @max000=maxn
		elsif minx.x<0 && max.x>0 && min.y<0 ### on Y <0
		  @min000=max
		  @max000=minx
		elsif minx.y>0 && min.y<0 && min.x>0 ### on X >0
		  @min000=minx
		  @max000=min
		elsif max.y>0 && maxn.y<0 && max.x<0 ### on X <0
		  @min000=maxn
		  @max000=max
		end
		@svec1=ORIGIN.vector_to(@min000)
		@svec2=ORIGIN.vector_to(@max000)
		@sangle=@svec1.angle_between(@svec2)
	  end
	  ### now we have @sangle
	  sangle=sprintf("%.1f", @sangle.radians)
	  prompt1=SPACS+" [R "+sangle+"°]: "
	  prompts=[prompt1]
	  values=[@@spaces1]
	when "RZ"
	  ### Test ORIGIN to selected and set limits, vectors, angle etc
	  min=@bb.min
	  min.z=0
	  max=@bb.max
	  max.z=0
	  minx=min.clone
	  minx.y=max.y
	  maxn=max.clone
	  maxn.y=min.y
	  ###
	  if @bb.contains?(ORIGIN)
	    ### test if it's internal OR on an edge OR on a corner
		if min.x==0 ### corner OR edge
		  if min.y==0 ### BL corner
			@svec1=Y_AXIS.clone
			@svec2=X_AXIS.clone
		    @sangle=90.degrees
		  elsif minx.y==0 ### TL
			@svec1=X_AXIS.clone
			@svec2=Y_AXIS.clone.reverse
		    @sangle=90.degrees
		  else ### on L edge
			@svec1=Y_AXIS.clone
			@svec2=Y_AXIS.clone.reverse
		    @sangle=180.degrees
		  end
		elsif max.x==0 ### corner OR edge
		  if maxn.y==0 ### BR corner
			@svec1=X_AXIS.clone.reverse
			@svec2=Y_AXIS.clone
		    @sangle=90.degrees
	      elsif max.y==0 ### TR
			@svec1=Y_AXIS.clone.reverse
			@svec2=X_AXIS.clone.reverse
		    @sangle=90.degrees
		  else ### R edge
			@svec1=Y_AXIS.clone.reverse
			@svec2=Y_AXIS.clone
		    @sangle=180.degrees
		  end 
		elsif min.y==0 ### B edge
		  @svec1=X_AXIS.clone.reverse
		  @svec2=X_AXIS.clone
		  @sangle=180.degrees
		elsif max.y==0 ### T edge
		  @svec1=X_AXIS.clone
		  @svec2=X_AXIS.clone.reverse
		  @sangle=180.degrees
		else ### wholly within bb
		  @svec1=X_AXIS.clone
		  @svec2=X_AXIS.clone.reverse
		  @sangle=360.degrees
		end
		@outsideBB=false
		@sdist=@diag
		###
	  else ### ORIGIN is outside bb
	    ###
		@outsideBB=true
		@sdist=@diag+ORIGIN.distance(@bb.center)
	    ### check which quadrant OR if straddles an axis
		if min.x>0 && min.y>0 ### TR
		  @min000=minx
		  @max000=maxn
    	elsif min.x==0 && min.y>0 ### TR on Y axis
		  @min000=min
		  @max000=maxn
    	elsif min.y==0 && min.x>0 ### TR on X axis
		  @min000=minx
		  @max000=min
		elsif min.x>0 && max.y<0 ### BR
		  @min000=max
		  @max000=min
		elsif min.x==0 && max.y<0 ### BR on Y axis
		  @min000=minx
		  @max000=max
		elsif max.y==0 && min.x>0 ### BR on X axis
		  @min000=minx
		  @max000=min
		elsif max.x<0 && max.y<0 ### BL
		  @min000=maxn
		  @max000=minx
		elsif max.x==0 && max.y<0 ### BL on Y axis
		  @min000=minx
		  @max000=max
		elsif max.y==0 && max.x<0 ### BL on X axis
		  @min000=maxn
		  @max000=max
		elsif min.y>0 && max.x<0 ### TL
		  @min000=min
		  @max000=max
		elsif min.y>0 && max.x==0 ### TL on Y axis
		  @min000=min
		  @max000=maxn
		elsif min.y==0 && max.y<0 ### TL on X axis
		  @min000=maxn
		  @max000=max
		elsif min.x<0 && maxn.x>0 && min.y>0 ### on Y >0
		  @min000=min
		  @max000=maxn
		elsif minx.x<0 && max.x>0 && min.y<0 ### on Y <0
		  @min000=max
		  @max000=minx
		elsif minx.y>0 && min.y<0 && min.x>0 ### on X >0
		  @min000=minx
		  @max000=min
		elsif max.y>0 && maxn.y<0 && max.x<0 ### on X <0
		  @min000=maxn
		  @max000=max
		end
		@svec1=ORIGIN.vector_to(@min000)
		@svec2=ORIGIN.vector_to(@max000)
		@sangle=@svec1.angle_between(@svec2)
	  end
	  ### now we have @sangle
	  sangle=sprintf("%.1f", @sangle.radians)
	  prompt1=SPACS+" [R "+sangle+"°]: "
	  prompt2=SPACS+" [Z "+@maxZ.to_l.to_s+"]: "
	  if @@spacing1==0 && @@spacing2==0
	    prompts=[prompt1,prompt2]
	    values=[@@spaces1, @@spaces2]
	  elsif @@spacing1==0
	    prompts=[prompt1]
	    values=[@@spaces1]
	  #elsif @@spacing2==0 ### ALWAYS spacing1==0 for RZ
	    #prompts=[prompt2]
	    #values=[@@spaces2]
	  end
	  ###
	else ### other axis ???
	  ### ???
  end#case
  ###
  title=@msg+SPACS
  results=inputbox(prompts,values,title)
  return nil unless results
  if @@spacing1==0 && @@spacing2==0
    @@spaces1, @@spaces2 = results
  elsif @@spacing1==0
    @@spaces1=results[0]
  elsif @@spacing2==0
    @@spaces2=results[0]
  end
  @@spaces1=1 if @@spacing1==0 && @@spaces1<1
  @@spaces2=1 if @@spacing2 && @@spacing2==0 && @@spaces2 && @@spaces2<1
  ###
  @spaces1=@@spaces1
  @spaces2=@@spaces2
  ###
  return true
  ###
end#dialog2
########### ############################################################

def layers_mats()
  ### make layers
  @layers=@model.layers
@layerXXXX=@layers.add("SLCR-XXXX")
@layerXXXX.visible=true
@layerYYYY=@layers.add("SLCR-YYYY")
@layerYYYY.visible=true
@layerZZZZ=@layers.add("SLCR-ZZZZ")
@layerZZZZ.visible=true
@layerAAAA=@layers.add("SLCR-AAAA")
@layerAAAA.visible=true
@layerRRRR=@layers.add("SLCR-RRRR")
@layerRRRR.visible=true
@layerFLAT=@layers.add("SLCR-FLAT")
@layerFLAT.visible=true
@layerTEXT=@layers.add("SLCR-TEXT")
@layerTEXT.visible=true
@layerOLIN=@layers.add("SLCR-OLIN")
@layerOLIN.visible=true
###
@layerHIDN=@layers.add("SLCR-HIDN")
@layerHIDN.visible=false
  ###
  @model.active_layer=nil
  ### make materials
  @mats=@model.materials
@matXXXX=@mats.add("SLCR-XXXX") unless @matXXXX=@mats["SLCR-XXXX"]
@matXXXX.color=[255,130,130]
@matXXXX.alpha=1.0
@matYYYY=@mats.add("SLCR-YYYY") unless @matYYYY=@mats["SLCR-YYYY"]
@matYYYY.color=[130,255,130]
@matYYYY.alpha=1.0
@matZZZZ=@mats.add("SLCR-ZZZZ") unless @matZZZZ=@mats["SLCR-ZZZZ"]
@matZZZZ.color=[130,130,255]
@matZZZZ.alpha=1.0
@matAAAA=@mats.add("SLCR-AAAA") unless @matAAAA=@mats["SLCR-AAAA"]
@matAAAA.color=[123,123,123]
@matAAAA.alpha=1.0
@matRRRR=@mats.add("SLCR-RRRR") unless @matRRRR=@mats["SLCR-RRRR"]
@matRRRR.color=[255,200,100]
@matRRRR.alpha=1.0
  ###
end#layers_mats
###
###
def run()
  ###
  @model.start_operation(S5, true)
  ###
  GC.enable()
  GC.start() ### garbage collection...
  ###
  @ents=@model.active_entities
  ###
  self.layers_mats()
  ###
  @ss.clear
  ###
  @rnd=sprintf("%.4d", (rand*10000))
  ###
  @spac1=@@spacing1
  @thick1=@@thickness1
  @insS1=@@inset_start1
  @insE1=@@inset_end1
  @spac2=@@spacing2
  @thick2=@@thickness2
  @insS2=@@inset_start2
  @insE2=@@inset_end2
  @angle=@@angle
  @trans0=@trans
  ###
  if @@ref==Y
    @ref=true
  else
    @ref=false
  end
  if @@flatten==Y
    @flat=true
  else
    @flat=false
  end
  if @@slot==Y
    @slot=true
  else ### No
    @slot=false
  end
  ###
  @tol=@@tol
  @bit=@@bit
  ###
  ### add central one then rep to start, repeat to end and insS1/insE1
  ### axes "X|Y|Z|XY|XZ|YZ"
  @vec1=nil
  @off1=nil
  @pref1=nil
  @vec2=nil
  @off2=nil
  @pref2=nil
  ###
  case @@axis
    when "Z"
	  @vec1=Geom::Vector3d.new(0,0,@spac1)
	  @vec1=Geom::Vector3d.new(0,0,1) if @spac1==0
	  @off1=@offZ
	  @pref1="Z"
	  @slot=false
    when "Y"
	  @vec1=Geom::Vector3d.new(0,@spac1,0)
	  @vec1=Geom::Vector3d.new(0,1,0) if @spac1==0
	  @off1=@offY
	  @pref1="Y"
	  @slot=false
    when "X"
	  @vec1=Geom::Vector3d.new(@spac1,0,0)
	  @vec1=Geom::Vector3d.new(1,0,0) if @spac1==0
	  @off1=@offX
	  @pref1="X"
	  @slot=false
	when "XZ"
	  @vec1=Geom::Vector3d.new(@spac1,0,0)
	  @vec1=Geom::Vector3d.new(1,0,0) if @spac1==0
	  @off1=@offX
	  @pref1="X"
	  @vec2=Geom::Vector3d.new(0,0,@spac2)
	  @vec2=Geom::Vector3d.new(0,0,1) if @spac2==0
	  @off2=@offZ
	  @pref2="Z"
    when "XY"
	  @vec1=Geom::Vector3d.new(@spac1,0,0)
	  @vec1=Geom::Vector3d.new(1,0,0) if @spac1==0
	  @off1=@offX
	  @pref1="X"
	  @vec2=Geom::Vector3d.new(0,@spac2,0)
	  @vec2=Geom::Vector3d.new(0,1,0) if @spac2==0
	  @off2=@offY
	  @pref2="Y"
    when "YZ"
	  @vec1=Geom::Vector3d.new(0,@spac1,0)
	  @vec1=Geom::Vector3d.new(0,1,0) if @spac1==0
	  @off1=@offY
	  @pref1="Y"
	  @vec2=Geom::Vector3d.new(0,0,@spac2)
	  @vec2=Geom::Vector3d.new(0,0,1) if @spac2==0
	  @off2=@offZ
	  @pref2="Z"
	when "A"
	  ### temporarily transform selected ### NOTE -ve angle !
	  @trA=Geom::Transformation.rotation(@cent, Z_AXIS, -@angle.degrees)
	  @selected.transform!(@trA)
	  @trans=@selected.transformation
	  ### redo bounds stuff
	  @bb=@selected.bounds
	  @max=@bb.max
	  @min=@bb.min
	  @maxZ=@max.z-@min.z
	  @maxY=@max.y-@min.y
	  @maxX=@max.x-@min.x
	  @offZ=@maxZ/2.0
	  @offY=@maxY/2.0
	  @offX=@maxX/2.0
	  @cent=@bb.center
	  @maxZC=@cent.clone
	  @maxZC.z=@max.z
	  @maxYC=@cent.clone
	  @maxYC.y=@max.y
	  @maxXC=@cent.clone
	  @maxXC.x=@max.x
	  @diag=@bb.diagonal
	  ###
	  @vec1=Geom::Vector3d.new(@spac1,0,0)
	  @vec1=Geom::Vector3d.new(1,0,0) if @spac1==0
	  @off1=@offX
	  @pref1="A"
	  @slot=false
	  ###
	when "AZ"
	  ### temporarily transform selected ### NOTE -ve angle !
	  @trA=Geom::Transformation.rotation(@cent, Z_AXIS, -@angle.degrees)
	  @selected.transform!(@trA)
	  @trans=@selected.transformation
	  ### redo bounds stuff
	  @bb=@selected.bounds
	  @max=@bb.max
	  @min=@bb.min
	  @maxZ=@max.z-@min.z
	  @maxY=@max.y-@min.y
	  @maxX=@max.x-@min.x
	  @offZ=@maxZ/2.0
	  @offY=@maxY/2.0
	  @offX=@maxX/2.0
	  @cent=@bb.center
	  @maxZC=@cent.clone
	  @maxZC.z=@max.z
	  @maxYC=@cent.clone
	  @maxYC.y=@max.y
	  @maxXC=@cent.clone
	  @maxXC.x=@max.x
	  @diag=@bb.diagonal
	  ###
	  @vec1=Geom::Vector3d.new(@spac1,0,0)
	  @vec1=Geom::Vector3d.new(1,0,0) if @spac1==0
	  @off1=@offX
	  @pref1="A"
	  ###
	  @vec2=Geom::Vector3d.new(0,0,@spac2)
	  @vec2=Geom::Vector3d.new(0,0,1) if @spac2==0
	  @off2=@offZ
	  @pref2="Z"
	  ###
	when "R"
	  ### set angles for each Radial slice later
	  @vec1="R"
	  @off1=@offX###unused
	  @pref1="R"
	  @slot=false
	  ###
	when "RZ"
	  @vec1="R"
	  @off1=@offX###unused
	  @pref1="R"
	  ###
	  @vec2=Geom::Vector3d.new(0,0,@spac2)
	  @vec2=Geom::Vector3d.new(0,0,1) if @spac2==0
	  @off2=@offZ
	  @pref2="Z"
	  ###
	else ### ???
	  ### ???
  end
  @slicesS=[]
  @slicesE=[]
  @slicesS2=[]
  @slicesS2=[]
  @sliceC=[]
  @slices=[]
  ###
  Sketchup.status_text=@msg+SLING+"..."
  self.slice(@vec1)
  @slices1=@slices.dup
  ###
  if @vec2
    @slicesS=[]
    @slicesE=[]
    @slicesS2=[]
    @slicesS2=[]
    @sliceC=[]
    @slices=[]
	###
	@off1=@off2
	###
    Sketchup.status_text=@msg+SLING+" 2..."
    self.slice(@vec2)
	###
	@slices2=@slices.dup
    @group2.name=@group2.name.gsub(/^SLICES/,"SLICES2")
	###
  end
  ###
  @slices=@slices1.dup
  Sketchup.status_text=@msg+STING+"..."
  self.slot() if @slot ### gp1
  ###
  if @vec2 && @slot ### gp2
    @slices=@slices2.dup
    Sketchup.status_text=@msg+STING+" 2..."
    self.slot2()
  end
  ###
  @slices=@slices1.dup
  if @thick1>0
    Sketchup.status_text=@msg+FTING+"..."
    self.fatten(@vec1) ### +tol if tol>0
  end
  ###
  Sketchup.status_text=@msg+ORING+"..."
  self.originate(@vec1)
  if @outline
    Sketchup.status_text=@msg+OLING+"..."
    self.outline()
  end
  Sketchup.status_text=@msg+RFING+"..."
  self.ref(@vec1,@pref1)
  Sketchup.status_text=@msg+MKING+"..."
  self.componentify()
  Sketchup.status_text=@msg+FLING+"..."
  self.flatten(@vec1) if @flat
  ###
  if @vec2
    @slices=@slices2.dup
	@off1=@off2
	###
    if @thick2>0
	  Sketchup.status_text=@msg+FTING+" 2..."
	  self.fatten(@vec2)
	end
	Sketchup.status_text=@msg+OLING+" 2..."
	self.originate(@vec2)
	Sketchup.status_text=@msg+RFING+" 2..."
    self.ref(@vec2,@pref2)
	Sketchup.status_text=@msg+MKING+" 2..."
	self.componentify()
	Sketchup.status_text=@msg+FLING+" 2..."
	self.flatten(@vec2) if @flat
  end
  ### if Angled then revert selected etc
  if @@axis=="A"
    @selected.transform!(@trA.inverse)
	@group1.transform!(@trA.inverse)
  elsif @@axis=="AZ"
    @selected.transform!(@trA.inverse)
	@group1.transform!(@trA.inverse)
	@group2.transform!(@trA.inverse) ###
  end
  ###
  @selected.layer=@layerHIDN
  ###
  @model.active_view.refresh
  ### export ?
=begin
  if @flat
    Sketchup.status_text=@msg+"Export ?"
	@dxf=false
    TIG::Slicer.export(@rnd, @flat1, @flat2)
  end
=end
  ###
  Sketchup.status_text=@msg+DONE
  ###
  @model.commit_operation unless @dxf
  ###
end#run

def outline()
  ents=@group1.entities.to_a
  ents.to_a.each{|gp|
    tr0=gp.transformation
	gents=gp.entities
	tgp=gents.add_group()
	tgp.name="OLIN"
	tgp.layer=@layerOLIN
	tgp.casts_shadows=false
	tgp.receives_shadows=false
	tents=tgp.entities
    es=gp.entities.intersect_with(true,tr0,tents,tr0,true,@group1)
	es.each{|e|e.erase! if e.valid? && e.bounds.center.z != @@thickness1}
  }
end

def slice(vec=Z_AXIS)
  gp=@ents.add_group()
  gents=gp.entities
  gp.name="SLICES-"+@rnd
  if vec=="R" #### it's radial
    @group1=gp
	spaces=@spaces1
	spaced=true
  elsif vec==@vec2 ### slices2
    @group2=gp
	if @spac2==0
	  vec.length=((@off1*2.0)-@insS2-@insE2-@thick2)/@spaces2
	  spaces=@spaces2
	  spaced=true
	else
	  spaced=false
	end
  else ## slices1
    @group1=gp
	if @spac1==0
	  vec.length=((@off1*2.0)-@insS1-@insE1-@thick1)/@spaces1
	  spaced=true
	  spaces=@spaces1
	else
	  spaced=false
	end
  end
  ###
 if spaced #############################################################
  ###
  if vec=="R"
    gp.layer=@layerRRRR
	gp.material=@matRRRR
  elsif vec.parallel?(Z_AXIS)
    gp.layer=@layerZZZZ
	gp.material=@matZZZZ
    pt=@maxZC.clone
	pt.z=pt.z-@insS1-(@thick1/2.0) if vec==@vec1
	pt.z=pt.z-@insS2-(@thick2/2.0) if @vec2 && vec==@vec2
	vec.reverse!
  elsif vec.parallel?(Y_AXIS)
    gp.layer=@layerYYYY
	gp.material=@matYYYY
    pt=@maxYC.clone
	pt.y=pt.y-@insS1-(@thick1/2.0) if vec==@vec1
	pt.y=pt.y-@insS2-(@thick2/2.0) if @vec2 && vec==@vec2
	vec.reverse!
  elsif vec.parallel?(X_AXIS) ### X or A
    gp.layer=@layerXXXX
	gp.material=@matXXXX
    pt=@maxXC.clone
	pt.x=pt.x-@insS1-(@thick1/2.0) if vec==@vec1
    pt.x=pt.x-@insS2-(@thick2/2.0) if @vec2 && vec==@vec2
	vec.reverse!
  end
  ###
  if @@axis=="A" || (@@axis=="AZ" && ! vec.parallel?(Z_AXIS))
    ### i.e. IT IS 'A' NOT 'Z' !
    gp.layer=@layerAAAA
	gp.material=@matAAAA
  end
  ### do slices
 if vec=="R" ### Radial slices
  ### adjust so 1st last slices are inset
  if @outsideBB
   dist1=ORIGIN.distance(@min000)
   if dist1>0
    alp1=Math.atan((@thick1/2.0)/dist1)
   else
    alp1=0.0
   end
   dist2=ORIGIN.distance(@max000)
   if dist2>0
    alp2=Math.atan((@thick1/2.0)/dist2)
   else
    alp2=0.0
   end
   ###
   vmin000=@svec1
   trv=Geom::Transformation.rotation(ORIGIN, Z_AXIS, -alp1)
   vmin000.transform!(trv)
   ###
   vmax000=@svec2
   trv=Geom::Transformation.rotation(ORIGIN, Z_AXIS, alp2)
   vmax000.transform!(trv)
   ###
   sangle=vmin000.angle_between(vmax000)
   ang=sangle/(spaces.to_f)
   ###
   vs=vmin000
  else ### it's internal
   vs=@svec1
   ang=@sangle/(spaces.to_f)
  end#if outsideBB
  ###
  trr=Geom::Transformation.rotation(ORIGIN, Z_AXIS, -ang)
  ###
  trv=Geom::Transformation.rotation(ORIGIN, Z_AXIS, -90.degrees)
  vs.transform!(trv)
  if @outsideBB
    spacs=spaces+1
  elsif @sangle==90.degrees ### still need extra one !
    spacs=spaces+1
  else ### miss one out as they overlap ! centrally OR on an edge !!
    spacs=spaces
  end
  ###
  (spacs).times{
    tgp=gents.add_group()
	tgp.set_attribute("Slicer5","vs",vs.to_a)
    tents=tgp.entities
	### circle except for R/RZ and @outsideBB==false and 90/180/360 then 
    ### use rectangle and start off from ORIGIN so so fouling
    if @@axis=~/^R/ && @sangle==360.degrees
      tr90=Geom::Transformation.rotation(ORIGIN, Z_AXIS, 90.degrees)
	  vvv=vs.transform(tr90)
	  toll=@tol
	  toll=0 unless toll
	  thick=@@thickness1
	  thick=1.mm if thick==0
      oset=((thick+toll)/2.0)*(1.0/Math.sin(ang/2.0))
	  p0=ORIGIN.offset(vvv, oset)
	  p0=ORIGIN.offset(vvv.reverse, oset) if @angle != 360.degrees
	  p1=p0.offset(Z_AXIS, @sdist)
	  p2=p1.offset(vvv, @sdist)
	  p3=p2.offset(Z_AXIS.reverse, @sdist*2)
	  p4=p3.offset(vvv.reverse, @sdist)
	  p5=p0.clone
	  ps=[p0,p1,p2,p3,p4,p5]
      @eds=tents.add_edges(ps)
	  fac=tents.add_face(ps[0..-2])
    else ### R/RZ outsideBB or on side/corner OR Axial
      @eds=tents.add_circle(ORIGIN, vs, @sdist, 12)###OUTSIDE BB
      fac=tents.add_face(@eds)
	end
	###	
    fac.reverse! if fac.normal!=vs.normalize
    tr0=tgp.transformation
    tr0=Geom::Transformation.new()
    tents.intersect_with(true,tr0,tents,tr0,true,@selected)
    self.tidy(tents,tr0)
	self.delete_unwanted_edges(tents)
    @slices << tgp
    vs.transform!(trr)
  }
  ###
 else ### NOT R == it's Axial slices
  ###
  (spaces+1).times{
    tgp=gents.add_group()
    tents=tgp.entities
	@eds=tents.add_circle(pt, vec, @diag, 12)### AXIAL
    fac=tents.add_face(@eds)
    fac.reverse! if fac.normal!=vec.normalize
    tr0=tgp.transformation
    tr0=Geom::Transformation.new()
    tents.intersect_with(true,tr0,tents,tr0,true,@selected)
    self.tidy(tents,tr0)
	self.delete_unwanted_edges(tents)
    @slices << tgp
	pt.offset!(vec)
  }
 end#if R
 ###
else ### IS by spacing #################################################
     ### NOTE THAT IT CAN'T BE 'R' !
  ###
  tgp=gents.add_group()
  tents=tgp.entities
  pt=@cent.clone
  if @@centn2==GAP && vec==@vec2
	pt.offset!(vec, vec.length/2.0)
  elsif @@centn1==GAP && (@@axis=="A" || (@@axis=="AZ" && ! vec.parallel?(Z_AXIS)))
    pt.offset!(vec, vec.length/2.0)
  elsif @@centn1==GAP && vec==@vec1
	pt.offset!(vec, vec.length/2.0)
  end
  @eds=tents.add_circle(pt, vec, @diag, 12)
  ###
  fac=tents.add_face(@eds)
  fac.reverse! if fac.normal!=vec.normalize
  tr0=tgp.transformation
  tr0=Geom::Transformation.new()
  tents.intersect_with(true,tr0,tents,tr0,true,@selected)
  ### clean up
  self.tidy(tents,tr0)
  self.delete_unwanted_edges(tents)
  ###
  @sliceC << tgp ### make central one 1st
  ###
 if vec==@vec2 ##### gp2
  ###
  if vec.parallel?(Z_AXIS)
    gp.layer=@layerZZZZ
	gp.material=@matZZZZ
    repS=((@offZ-@insS2-(@thick2/2.0))/@spac2).to_i
    repE=((@offZ-@insE2-(@thick2/2.0))/@spac2).to_i
  elsif vec.parallel?(Y_AXIS)
    gp.layer=@layerYYYY
	gp.material=@matYYYY
    repS=((@offY-@insS2-(@thick2/2.0))/@spac2).to_i
    repE=((@offY-@insE2-(@thick2/2.0))/@spac2).to_i
  elsif vec.parallel?(X_AXIS) ### X or A
    gp.layer=@layerXXXX
	gp.material=@matXXXX
    repS=((@offX-@insS2-(@thick2/2.0))/@spac2).to_i
    repE=((@offX-@insE2-(@thick2/2.0))/@spac2).to_i
  end
  insS=@insS2
  insE=@insE2
  thick=@thick2
  ###
 else ########## gp1 ###########################################################
  ###
  if vec.parallel?(Z_AXIS)
    gp.layer=@layerZZZZ
	gp.material=@matZZZZ
    repS=((@offZ-@insS1-(@thick1/2.0))/@spac1).to_i
    repE=((@offZ-@insE1-(@thick1/2.0))/@spac1).to_i
  elsif vec.parallel?(Y_AXIS)
    gp.layer=@layerYYYY
	gp.material=@matYYYY
    repS=((@offY-@insS1-(@thick1/2.0))/@spac1).to_i
    repE=((@offY-@insE1-(@thick1/2.0))/@spac1).to_i
  elsif vec.parallel?(X_AXIS) ### X or A
    gp.layer=@layerXXXX
	gp.material=@matXXXX
    repS=((@offX-@insS1-(@thick1/2.0))/@spac1).to_i
    repE=((@offX-@insE1-(@thick1/2.0))/@spac1).to_i
  end
  if @@axis=="A" || (@@axis=="AZ" && vec.parallel?(@vec1))
    gp.layer=@layerAAAA
	gp.material=@matAAAA
  end
  insS=@insS1
  insE=@insE1
  thick=@thick1
  ###
 end ##### gp1/gp2
  ### For gp1 OR gp2
  ### to Start
  pt=@cent.clone
  if @@centn2==GAP && vec==@vec2
	pt.offset!(vec, vec.length/2.0)
  elsif @@centn1==GAP && (@@axis=="A" || (@@axis=="AZ" && ! vec.parallel?(Z_AXIS)))
    pt.offset!(vec, vec.length/2.0)
  elsif @@centn1==GAP && vec==@vec1
	pt.offset!(vec, vec.length/2.0)
  end
  (repS).times{|i|
	pt.offset!(vec)
    tgp=gents.add_group()
    tents=tgp.entities
    @eds=tents.add_circle(pt, vec, @diag, 12)
    fac=tents.add_face(@eds)
    fac.reverse! if fac.normal!=vec.normalize
    tr0=tgp.transformation
	tr0=Geom::Transformation.new()
    tents.intersect_with(true,tr0,tents,tr0,true,@selected)
	### clean up
    self.tidy(tents,tr0)
	self.delete_unwanted_edges(tents)
    ###
	@slicesS << tgp
  }#times
  ### first
  tvec=vec.clone
  tvec.length=@off1-insS-(thick/2.0)
  ptt=@cent.offset(tvec)
  unless pt == ptt
    tgp=gents.add_group()
    tents=tgp.entities
    @eds=tents.add_circle(ptt, vec, @diag, 12)
    fac=tents.add_face(@eds)
    fac.reverse! if fac.normal!=vec.normalize
    tr0=tgp.transformation
	tr0=Geom::Transformation.new()
    tents.intersect_with(true,tr0,tents,tr0,true,@selected)
	### clean up
    self.tidy(tents,tr0)
	self.delete_unwanted_edges(tents)
    @slicesS << tgp
  end
  ###
  ### to End
  pt=@cent.clone
  if @@centn2==GAP && vec==@vec2
	pt.offset!(vec, vec.length/2.0)
	repE+=1
  elsif @@centn1==GAP && (@@axis=="A" || (@@axis=="AZ" && ! vec.parallel?(Z_AXIS)))
    pt.offset!(vec, vec.length/2.0)
	repE+=1
  elsif @@centn1==GAP && vec==@vec1
	pt.offset!(vec, vec.length/2.0)
	repE+=1
  end
  (repE).times{|i|
	pt.offset!(vec.reverse)
    tgp=gents.add_group()
    tents=tgp.entities
    @eds=tents.add_circle(pt, vec, @diag, 12)
    fac=tents.add_face(@eds)
    fac.reverse! if fac.normal!=vec.normalize
    tr0=tgp.transformation
	tr0=Geom::Transformation.new()
    tents.intersect_with(true,tr0,tents,tr0,true,@selected)
	### clean up
    self.tidy(tents,tr0)
	self.delete_unwanted_edges(tents)
    ###
	@slicesE << tgp
  }#times
  ### last
  tvec=vec.clone.reverse
  tvec.length=@off1-insE-(thick/2.0)
  ptt=@cent.offset(tvec)
  unless pt == ptt
    tgp=gents.add_group()
    tents=tgp.entities
    @eds=tents.add_circle(ptt, vec, @diag, 12)
    fac=tents.add_face(@eds)
    fac.reverse! if fac.normal!=vec.normalize
    tr0=tgp.transformation
	tr0=Geom::Transformation.new()
    tents.intersect_with(true,tr0,tents,tr0,true,@selected)
	### clean up
    self.tidy(tents,tr0)
	self.delete_unwanted_edges(tents)
    @slicesE << tgp
  end
  ### assemble groups in order and number etc...
  @slices = @slicesS.reverse + @sliceC + @slicesE
  ###
 end#if spaced #########################################################
  ###
end#slice


def tidy(tents=[], tr0=Geom::Transformation.new())
  ### REMOVE OUTER RING
  tents.erase_entities(@eds)
  #@model.active_view.refresh
   #UI.messagebox(tents.to_s)
   ### REMOVE SPURIOUS EDGES
  self.delete_spurious_edges(tents)
   #@model.active_view.refresh
   #UI.messagebox(tents.to_s)
  ### HEAL TINY GAPS
  edges=tents.grep(Sketchup::Edge)
  vs=[]
  edges.each{|e| vs << e.vertices }
  vs.flatten!
  vs.uniq!
  vs.dup.each{|v| vs.delete(v) if v.edges[1] }
  vs.dup.each{|v|
	d=1000000
	vvv=nil
	(vs-[v]).each{|vv|
		next if vv.edges.include?(v.edges[0])
		dd=v.position.distance(vv.position)
		if dd < d
			vvv=vv
			d=dd
		end
	}
	if vvv
		tents.add_line(v, vvv)
	end
	vs.delete(vvv)
  }
  ### MAKE FACES
  4.times{
    tents.grep(Sketchup::Edge).each{|e| e.find_faces unless e.faces[0] }
    tents.intersect_with(true,tr0,tents,tr0,true,tents.to_a)
  }#times
  ###
  ### 'BREAK' ALL BOWTIE FACES ?
  
  ###
  #tents.grep(Sketchup::Edge).each{|e|e.erase! unless e.faces[0]}
  ### MAKE HOLES
  faces=tents.grep(Sketchup::Face)
  (faces.length-1).times{
	faces.each{|face|
		next unless face.valid?
		face.outer_loop.edgeuses.each{|edgeuse|
			next if edgeuse.partners[0]### it's the outermost face
			faces=faces-[face]
			loops=face.loops
			loops.each{|loop|
			   faces.each{|fac|
				  next unless fac.valid?
				  if (fac.outer_loop.edges-loop.edges)==[]
					 faces=faces-[fac]
					 fac.erase! if fac.valid? ### fac abutts kept face so it must be erased...
				  end #if fac
			   } #for fac
			}#for loops
		}#for edgeuse
	}#for face
  }#times
  #@model.active_view.refresh
  ###
end#tidy


def fatten(vec=Z_AXIS)
  if vec=="R"
    thick=(@thick1/2.0)
  elsif vec==@vec2
    thick=(@thick2/2.0)
  else
    thick=(@thick1/2.0)
  end
  @slices.each{|gp|
	gents=gp.entities
    faces=gents.grep(Sketchup::Face)
	next unless faces[0]
	vecp=faces[0].normal
	faces.each{|face| face.pushpull(thick) if face.valid? }
	faces=gents.grep(Sketchup::Face)
	faces.dup.each{|face| faces.delete(face) unless ! face.valid? || face.normal==vecp.reverse.normalize }
	faces.each{|face| face.pushpull(thick) if face.valid? }
	self.delete_flaps(gents)
  }
end#fatten


def originate(vec=Z_AXIS)
 if vec=="R"
  @slices.each{|gp|
    gents=gp.entities
	###
    bb=gp.bounds
	pt=bb.min
	ve=pt.vector_to(ORIGIN)
	tr=Geom::Transformation.translation(ve)
	gents.transform_entities(tr,gents.to_a)
	gp.transform!(tr.inverse)
	gp.entities.parent.invalidate_bounds
    ###
	vs=gp.get_attribute("Slicer5","vs",nil)
	if vs && vs.y>=0
	  lie=true
	else
	  lie=false
	end
	ps=[]
	gents.grep(Sketchup::Edge).each{|e|
	  p0=e.start.position.to_a
	  p0.z=0
	  ps << p0
      p1=e.end.position.to_a
	  p1.z=0
	  ps << p1
	}
	ps.uniq!
	pss=[]
	ps.each{|p|
	  if lie
	    pss << p if p.y==0
	  else
	    pss << p if p.x==0
	  end
	}
	next unless pss[0]
	pt=pss[0]
	pt.z=0
	pss.each{|p|
	  if lie
	    pt=p if p.x<pt.x
	  else
	    pt=p if p.y<pt.y
	  end
    }
	ve=pt.vector_to(ORIGIN)
	tr=Geom::Transformation.translation(ve)
	gents.transform_entities(tr,gents.to_a)
	gp.transform!(tr.inverse)
	gp.entities.parent.invalidate_bounds
	###
	if vs
	  ang=X_AXIS.angle_between(vs)
	  if lie
	    ang = -ang
	  else
	    ang = ang
	  end
	else
	  ang=0.0
	end
	tr=Geom::Transformation.rotation(ORIGIN, Z_AXIS, ang)
	gents.transform_entities(tr,gents.to_a)
	tr=Geom::Transformation.rotation(gp.transformation.origin, Z_AXIS, -ang)
	gp.transform!(tr)
	gp.entities.parent.invalidate_bounds
	###
	tr=Geom::Transformation.rotation(ORIGIN, Y_AXIS, -90.degrees)
	gents.transform_entities(tr,gents.to_a)
	tr=Geom::Transformation.rotation(gp.transformation.origin, gp.transformation.yaxis, 90.degrees)
	gp.transform!(tr)
	gp.entities.parent.invalidate_bounds
	### ensure all geom above z=0
	minz=0
	gents.grep(Sketchup::Edge).each{|e|
	  minz=e.start.position.z if e.start.position.z < minz
	  minz=e.end.position.z if e.end.position.z < minz
	}
	if vs && minz < 0
	  tr=Geom::Transformation.translation([0, 0, minz.abs])
	  gents.transform_entities(tr,gents.to_a)
	  vv=Geom::Vector3d.new(vs.x, vs.y, vs.z)
	  vv.length=minz.abs
	  tr=Geom::Transformation.translation(vv.reverse)
	  gp.transform!(tr)
	  gp.entities.parent.invalidate_bounds
	end
	### ensure origin at y min
	miny=0
	gents.grep(Sketchup::Edge).each{|e|
	  miny=e.start.position.y if e.start.position.y < miny
	  miny=e.end.position.y if e.end.position.y < miny
	}
	if vs && miny < 0
	  tr=Geom::Transformation.translation([0, miny.abs, 0])
	  gents.transform_entities(tr,gents.to_a)
	  vv=gp.transformation.yaxis.reverse
	  vv.length=miny.abs
	  tr=Geom::Transformation.translation(vv)
	  gp.transform!(tr)
	  gp.entities.parent.invalidate_bounds
	end
	### remove attribute dict
	gp.attribute_dictionaries.delete("Slicer5")
	###
  }
 else ### NOT R == AXIAL
  @slices.each{|gp|
	gents=gp.entities
    bb=gp.bounds
	if vec.parallel?(Z_AXIS)
	  if @@axis=="AZ" ### relocate
        pt=bb.min
	    ve=pt.vector_to(ORIGIN)
	    tr=Geom::Transformation.translation(ve)
	    gents.transform_entities(tr,gents.to_a)
	    gp.transform!(tr.inverse)
	    gp.entities.parent.invalidate_bounds
		### rotate back
		tr=Geom::Transformation.rotation(ORIGIN,Z_AXIS,@@angle.degrees)
		gents.transform_entities(tr,gents.to_a)
		tr=Geom::Transformation.rotation(gp.transformation.origin,Z_AXIS,-@@angle.degrees)
	    gp.transform!(tr)
		gp.entities.parent.invalidate_bounds
		### slice so orig at bb.min
		pt=[1000000,1000000,0]
		vs=[]
		gents.grep(Sketchup::Edge).each{|e|vs<<e.vertices}
		vs.flatten!
		vs.uniq!
		ps=[]
		vs.each{|v| ps<<v.position }
		ps.each{|p|
		  pt.x=p.x if p.x<pt.x
		  pt.y=p.y if p.y<pt.y
		}
	    ve=pt.vector_to(ORIGIN)
	    tr=Geom::Transformation.translation(ve)
	    gents.transform_entities(tr,gents.to_a)
		###
		trr=Geom::Transformation.rotation(ORIGIN,Z_AXIS,-@@angle.degrees)
		ve.reverse!
		ve.transform!(trr)
	    tr=Geom::Transformation.translation(ve)
	    gp.transform!(tr)
		###
	    gp.entities.parent.invalidate_bounds
		###
	  else ### it's plain Z slices
	    ###
		pt=bb.min
	    ve=pt.vector_to(ORIGIN)
	    tr=Geom::Transformation.translation(ve)
	    gents.transform_entities(tr,gents.to_a)
	    gp.transform!(tr.inverse)
	    gp.entities.parent.invalidate_bounds
	  end
    elsif vec.parallel?(Y_AXIS)
	  ### relocate
      pt=bb.min
	  pt.y=pt.y+@thick1 if vec==@vec1
	  pt.y=pt.y+@thick2 if vec==@vec2
	  ve=pt.vector_to(ORIGIN)
	  tr=Geom::Transformation.translation(ve)
	  gents.transform_entities(tr,gents.to_a)
	  gp.transform!(tr.inverse)
	  gp.entities.parent.invalidate_bounds
	  ### rotate
	  gpt=gp.transformation
	  opt=gpt.origin
	  tr=Geom::Transformation.rotation(ORIGIN, X_AXIS, -90.degrees)
	  gents.transform_entities(tr,gents.to_a)
	  tr=Geom::Transformation.rotation(opt, X_AXIS, 90.degrees)
	  gp.transform!(tr)
	  gp.entities.parent.invalidate_bounds
    elsif vec.parallel?(X_AXIS) ### X or A
	  ### relocate
      pt=bb.min
	  ve=pt.vector_to(ORIGIN)
	  tr=Geom::Transformation.translation(ve)
	  gents.transform_entities(tr,gents.to_a)
	  gp.transform!(tr.inverse)
	  gp.entities.parent.invalidate_bounds
	  ### rotate
	  gpt=gp.transformation
	  opt=gpt.origin
	  tr=Geom::Transformation.rotation(ORIGIN, Y_AXIS, -90.degrees)
	  gents.transform_entities(tr,gents.to_a)
	  tr=Geom::Transformation.rotation(opt, Y_AXIS, 90.degrees)
	  gp.transform!(tr)
	  gp.entities.parent.invalidate_bounds
    end
  }
 end#if R
end

def ref(vec=Z_AXIS, pref="?")
  ### rename groups... then perhaps add ref code if @ref
  ###
  num=pref+"001"
  ###
  failure=false
  @slices.each{|gp|
	break if failure
    gp.name=num
	num.next!
	if @ref
      txt=gp.name
	  txgp=gp.entities.add_group()
	  txgp.name="TEXT"
	  txgp.layer=@layerTEXT
	  txgp.set_attribute("Slicer5", "ref", txt)
	  txgp.entities.add_3d_text(txt,TextAlignLeft,'Txt',false,false,@@tht,0,0,false,0)
	  if vec==@vec2
	    thick=@thick2
	  else
	    thick=@thick1
	  end
	  tt=[@@tht, @@tht, thick]
	  begin
		tm=txgp.bounds.min
		ve=tm.vector_to(tt)
		tr=Geom::Transformation.translation(ve)
		txgp.transform!(tr)
		if vec=="R" || vec.parallel?(X_AXIS)
			tr=Geom::Transformation.rotation(ORIGIN,Z_AXIS,90.degrees)
			txgp.transform!(tr)
		else ###
			### do nothing because it's already Y or Z
		end
	  rescue
		if RUBY_PLATFORM =~ /mswin|mingw/ ### PC
			UI.messagebox("Slicer Error:\n\nCreation of 'Ref' failed !")
		else
			UI.messagebox("Slicer Error:\n\nCreation of 'Ref' failed.\nAs a MAC user you must have the Font 'txt.ttf' installed.\n Get its ZIP archive from the link on the Slicer Document page @PluginStore...")
		end
		failure=true
	  end
    end
  }
end#ref

def componentify()
  @defs=[]
  @slices.each{|gp|
    name=gp.name
    ins=gp.to_component
	defn=ins.definition
	defn.name=@rnd+"-"+name
	@defs << defn
	defn.invalidate_bounds ###
  }
end#componentify

def flatten(vec=Z_AXIS)
  gp=@ents.add_group()
  gp.name="FLAT-"+@rnd
  gp.layer=@layerFLAT
  @flat1=gp if vec!=@vec2
  nudge=false
  if vec=="R"
    gp.material=@matRRRR
  elsif vec.parallel?(Z_AXIS)
	gp.material=@matZZZZ
  elsif vec.parallel?(Y_AXIS)
	gp.material=@matYYYY
  elsif vec.parallel?(X_AXIS)### X or A
	gp.material=@matXXXX
	nudge=true
  end
  if @@axis=="A" || (@@axis=="AZ" && vec.parallel?(@vec1))
    gp.material=@matAAAA
  end
  gents=gp.entities
  pt=@selected.bounds.max
  pt.z=0
  pt.x=pt.x+@selected.bounds.depth if vec=="R" || vec.parallel?(X_AXIS)
  if vec==@vec2 ### put second flat set UP
    pt.y=pt.y+@thick1+@y2-@selected.bounds.max.y
	gp.name="FLAT2-"+@rnd
	@flat2=gp
  end
  pt.offset!([@thick1,@thick1,0])
  ### arranger
  num=@defs.length
  numx=Math::sqrt(num).round
  numx=1 if numx<1
  ctrx=0
  px=pt.x
  py=pt.y
  ###
  @defs.each{|defn|
    defn.invalidate_bounds
    tr=Geom::Transformation.new(pt)
    ins=gents.add_instance(defn, tr)
	if ins.bounds.min.y<pt.y
	  vv=[0, ins.bounds.height, 0]
	  trv=Geom::Transformation.translation(vv)
	  ins.transform!(trv)
	end
	gp.entities.parent.invalidate_bounds
	if vec=="R" || vec.parallel?(X_AXIS)
	  pt.x=pt.x+@selected.bounds.depth+@thick1
	else
	  pt.x=pt.x+ins.bounds.width+@thick1
	end
	###
	ctrx+=1
	if ctrx>=numx
	  gp.entities.parent.invalidate_bounds
	  pt.x=px
	  pt.y=py+@thick1+gp.bounds.height
	  ctrx=0
	end
	###
  }
  gp.entities.parent.invalidate_bounds
  @y2=@thick1+gp.bounds.max.y
end#flatten

##############################
def delete_flaps(ents)
  edges=ents.grep(Sketchup::Edge)
  edges.length.times{
    togos=[]
	edges.each{|e|
		next unless e.valid?
		if e.faces.length==1
		  togos << e
		end
	}
    ents.erase_entities(togos)
  }
  ###
  edges=ents.grep(Sketchup::Edge)
  togos=[]
  edges.each{|e|
    next unless e.valid?
    if e.faces.length==0
      togos << e
    end
  }
  ents.erase_entities(togos)
  ###
end

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

def slot()
  ### slot gp1 with gp2
  @group1.entities.grep(Sketchup::Group).each{|gp|
    next unless gp.valid?
    gents=gp.entities
	### remove faceless/coplanar
	#self.delete_unwanted_edges(gents)
	###
	tr0=Geom::Transformation.new()
	trg=gp.transformation
	###
	edgs=gents.intersect_with(true, trg, gents, tr0, true, @group2)
	#return
    ### replace with half length line
	neds=[]
	edgs.each{|e|
	  next unless e.valid?
	  ve=e.line[1]
	  mpt=e.start.position.offset(ve, e.length/2)
	  neds << mpt
	  e.erase! if e.valid?
	}
	neds.each{|mpt|
	  ###
	  if @vec1=="R"
	    ###
		p0=mpt.offset([0,0,1], (@thick2/2.0)+(@tol/2.0))
		p1=p0.offset([0,0,-1], @thick2+@tol)
		vs=[1,0,0]
		gents.grep(Sketchup::Face).each{|e|
		    vs=e.normal
			break
		}
		if @bit>0
		  tr=Geom::Transformation.rotation(mpt, Z_AXIS, -90.degrees)
		  vp=vs.transform(tr)
		  vp.length=@bit/2.0
	      p0.offset!(vp)
		  p1.offset!(vp)
		end
		###
		tr=Geom::Transformation.rotation(mpt, Z_AXIS, 90.degrees)
		vp=vs.transform(tr)
		vp.reverse! ###if @sangle != 360.degrees ### ?
		###
		ga=gents.to_a
		 ###
		  ln=gents.add_line(p0, p1) ### slot end
		  ray=@model.raytest([p0, vp])
		  if ray
		    p00=ray[0]
		  else
		    p00=p0.offset([0, 0, @diag])
		  end
		  e00=gents.add_line(p0, p00)
		  ray=@model.raytest([p1, vp])
		  if ray
		    p11=ray[0]
		  else
		    p11=p1.offset([0, 0, @diag])
		  end
		  e11=gents.add_line(p1, p11)
		  gents.intersect_with(true, tr0, gents, tr0, true, ga)
		  ln.find_faces if ln.valid?
		  if e00 && e00.valid? && e00.faces[1]
			if e00.faces[0].area<e00.faces[1].area
			  e00.faces[0].erase!
			else
			  e00.faces[1].erase!
			end
		  end
		  if e11 && e11.valid? && e11.faces[1]
			if e11.faces[0].area<e11.faces[1].area
			  e11.faces[0].erase!
			else
			  e11.faces[1].erase!
			end
		  end
		 ###
		###
	  elsif @vec1.parallel?(X_AXIS) ### X or A
	    ###
	    if @vec2.parallel?(Y_AXIS) ### X & Y
	      p0=mpt.offset([0, -((@thick2/2.0)+(@tol/2.0)), 0])
		  p1=p0.offset([0, @thick2+@tol, 0])
		  ### move p0/p1 by bit/2
		  if @bit>0
		    vp=[0, 0, -@bit/2.0]
			p0.offset!(vp)
			p1.offset!(vp)
		  end
		  ga=gents.to_a
		  ln=gents.add_line(p0, p1) ### slot end
		  ray=@model.raytest([p0, [0, 0, 1]])
		  if ray
		    p00=ray[0]
		  else
		    p00=p0.offset([0, 0, @diag])
		  end
		  e00=gents.add_line(p0, p00)
		  ray=@model.raytest([p1, [0, 0, 1]])
		  if ray
		    p11=ray[0]
		  else
		    p11=p1.offset([0, 0, @diag])
		  end
		  e11=gents.add_line(p1, p11)
		  gents.intersect_with(true, tr0, gents, tr0, true, ga)
		  ln.find_faces if ln.valid?
		  if e00 && e00.valid? && e00.faces[1]
			if e00.faces[0].area<e00.faces[1].area
			  e00.faces[0].erase!
			else
			  e00.faces[1].erase!
			end
		  end
		  if e11 && e11.valid? && e11.faces[1]
			if e11.faces[0].area<e11.faces[1].area
			  e11.faces[0].erase!
			else
			  e11.faces[1].erase!
			end
		  end
		  ###
		else ### X & Z
		  ###
	      p0=mpt.offset([0 ,0, -((@thick2/2.0)+(@tol/2.0))])
		  p1=p0.offset([0, 0, @thick2+@tol])
		  ### move p0/p1 by bit/2
		  if @bit>0
		    vp=[0, -@bit/2.0, 0]
			p0.offset!(vp)
			p1.offset!(vp)
		  end
		  ga=gents.to_a
		  ln=gents.add_line(p0, p1) ### slot end
		  ray=@model.raytest([p0, [0, 1, 0]])
		  if ray
		    p00=ray[0]
		  else
		    p00=p0.offset([0, @diag, 0])
		  end
		  e00=gents.add_line(p0, p00)
		  ray=@model.raytest([p1, [0, 1, 0]])
		  if ray
		    p11=ray[0]
		  else
		    p11=p1.offset([0, @diag, 0])
		  end
		  e11=gents.add_line(p1, p11)
		  gents.intersect_with(true, tr0, gents, tr0, true, ga)
		  ln.find_faces if ln.valid?
		  if e00 && e00.valid? && e00.faces[1]
			if e00.faces[0].area<e00.faces[1].area
			  e00.faces[0].erase!
			else
			  e00.faces[1].erase!
			end
		  end
		  if e11 && e11.valid? && e11.faces[1]
			if e11.faces[0].area<e11.faces[1].area
			  e11.faces[0].erase!
			else
			  e11.faces[1].erase!
			end
		  end
		  ###
		end
	  ###
	  else ### parallel to Y_AXIS >>> Y & Z only
	  ###
	    ###
	      p0=mpt.offset([0, 0, -((@thick2/2.0)+(@tol/2.0))])
		  p1=p0.offset([0, 0, @thick2+@tol])
		  ### move p0/p1 by bit/2
		  if @bit>0
		    vp=[-@bit/2.0, 0, 0]
			p0.offset!(vp)
			p1.offset!(vp)
		  end
		  ga=gents.to_a
		  ln=gents.add_line(p0, p1) ### slot end
		  ray=@model.raytest([p0, [1, 0, 0]])
		  if ray
		    p00=ray[0]
		  else
		    p00=p0.offset([@diag, 0, 0])
		  end
		  e00=gents.add_line(p0, p00)
		  ray=@model.raytest([p1, [1, 0, 0]])
		  if ray
		    p11=ray[0]
		  else
		    p11=p1.offset([@diag, 0, 0])
		  end
		  e11=gents.add_line(p1, p11)
		  gents.intersect_with(true, tr0, gents, tr0, true, ga)
		  ln.find_faces if ln.valid?
		  if e00 && e00.valid? && e00.faces[1]
			if e00.faces[0].area<e00.faces[1].area
			  e00.faces[0].erase!
			else
			  e00.faces[1].erase!
			end
		  end
		  if e11 && e11.valid? && e11.faces[1]
			if e11.faces[0].area<e11.faces[1].area
			  e11.faces[0].erase!
			else
			  e11.faces[1].erase!
			end
		  end
		###
	  end
	  ###
	}
	###
    self.delete_unwanted_edges(gents)
	###
  }
 ###
end#slot

def slot2()
  ### slot gp2 with gp1
  @group2.entities.grep(Sketchup::Group).each{|gp|
    next unless gp.valid?
    gents=gp.entities
	### remove faceless/coplanar
	#self.delete_unwanted_edges(gents)
	###
	tr0=Geom::Transformation.new()
	trg=gp.transformation
	###
	edgs=gents.intersect_with(true, trg, gents, tr0, true, @group1)
	#return
    ### get half length line
	vecs=[]
	neds=[]
	edgs.each{|e|
	  next unless e.valid?
	  if @vec2.parallel?(Y_AXIS) ### || X >>> Z
	    if e.start.position.z > e.end.position.z
	      mpt=e.start.position
		else
		  mpt=e.end.position
		end
	  else ### || Z
	    if @vec1=="R" ### RZ
		  org=gp.transformation.inverse.origin
		  if org.distance(e.start.position)<org.distance(e.end.position)
			mpt=e.start.position
			vecs << e.line[1]
		  else
		    mpt=e.end.position
			vecs << e.line[1].reverse
		  end
		elsif @vec1.parallel?(X_AXIS) ### || Z >>> y
	      if e.start.position.y > e.end.position.y
	        mpt=e.start.position
		  else
		    mpt=e.end.position
		  end
		else ### vec1 || Y >>> x
		  if e.start.position.x > e.end.position.x
	        mpt=e.start.position
		  else
		    mpt=e.end.position
		  end
		end
	  end
	  neds << mpt
	  e.erase! if e.valid?
	}
	neds.each_with_index{|mpt, i|
	  ###
	  if @vec2.parallel?(Y_AXIS) ### with X
	    ###
	      p0=mpt.offset([-((@thick1/2.0)+(@tol/2.0)), 0, 0])
		  p1=p0.offset([@thick1+@tol, 0, 0])
		  ### move p0/p1 by bit/2
		  if @bit>0
		    vp=[0, 0, @bit/2.0]
			p0.offset!(vp)
			p1.offset!(vp)
		  end
		  ga=gents.to_a
		  ln=gents.add_line(p0, p1) ### slot end
		  ray=@model.raytest([p0, [0, 0, -1]])
		  if ray
		    p00=ray[0]
		  else
		    p00=p0.offset([0, 0, -@diag])
		  end
		  e00=gents.add_line(p0, p00)
		  ray=@model.raytest([p1, [0, 0, -1]])
		  if ray
		    p11=ray[0]
		  else
		    p11=p1.offset([0, 0, -@diag])
		  end
		  e11=gents.add_line(p1, p11)
		  gents.intersect_with(true, tr0, gents, tr0, true, ga)
		  ln.find_faces if ln.valid?
		  if e00 && e00.valid? && e00.faces[1]
			if e00.faces[0].area<e00.faces[1].area
			  e00.faces[0].erase!
			else
			  e00.faces[1].erase!
			end
		  end
		  if e11 && e11.valid? && e11.faces[1]
			if e11.faces[0].area<e11.faces[1].area
			  e11.faces[0].erase!
			else
			  e11.faces[1].erase!
			end
		  end
		###
	  else ### Z & X OR Y
	    ###
		if @vec1=="R" ### RZ !
		 ###
		  tr=Geom::Transformation.rotation(mpt, Z_AXIS, 90.degrees)
		  vec=vecs[i]
		  vpp=vec.transform(tr)
		  p0=mpt.offset(vpp, (@thick1/2.0)+(@tol/2.0))
		  p1=p0.offset(vpp.reverse, @thick1+@tol)
		  ### move p0/p1 by bit/2
		  if @bit>0
		    vp=[0, @bit/2.0, 0]
			p0.offset!(vec.reverse)
			p1.offset!(vec.reverse)
		  end
		  ga=gents.to_a
		  ln=gents.add_line(p0, p1) ### slot end
		  ray=@model.raytest([p0, vec])
		  if ray
		    p00=ray[0]
		  else
		    p00=p0.offset(vec, @diag)
		  end
		  e00=gents.add_line(p0, p00)
		  ray=@model.raytest([p1, vec])
		  if ray
		    p11=ray[0]
		  else
		    p11=p1.offset(vec, @diag)
		  end
		  e11=gents.add_line(p1, p11)
		  gents.intersect_with(true, tr0, gents, tr0, true, ga)
		  ln.find_faces if ln.valid?
		  if e00 && e00.valid? && e00.faces[1]
			if e00.faces[0].area<e00.faces[1].area
			  e00.faces[0].erase!
			else
			  e00.faces[1].erase!
			end
		  end
		  if e11 && e11.valid? && e11.faces[1]
			if e11.faces[0].area<e11.faces[1].area
			  e11.faces[0].erase!
			else
			  e11.faces[1].erase!
			end
		  end
		  ###
		elsif @vec1.parallel?(X_AXIS) ### X & Z
		  ###
	      p0=mpt.offset([-((@thick1/2.0)+(@tol/2.0)), 0, 0])
		  p1=p0.offset([@thick1+@tol, 0, 0])
		  ### move p0/p1 by bit/2
		  if @bit>0
		    vp=[0, @bit/2.0, 0]
			p0.offset!(vp)
			p1.offset!(vp)
		  end
		  ga=gents.to_a
		  ln=gents.add_line(p0, p1) ### slot end
		  ray=@model.raytest([p0, [0, -1, 0]])
		  if ray
		    p00=ray[0]
		  else
		    p00=p0.offset([0, -@diag, 0])
		  end
		  e00=gents.add_line(p0, p00)
		  ray=@model.raytest([p1, [0, -1, 0]])
		  if ray
		    p11=ray[0]
		  else
		    p11=p1.offset([0, -@diag, 0])
		  end
		  e11=gents.add_line(p1, p11)
		  gents.intersect_with(true, tr0, gents, tr0, true, ga)
		  ln.find_faces if ln.valid?
		  if e00 && e00.valid? && e00.faces[1]
			if e00.faces[0].area<e00.faces[1].area
			  e00.faces[0].erase!
			else
			  e00.faces[1].erase!
			end
		  end
		  if e11 && e11.valid? && e11.faces[1]
			if e11.faces[0].area<e11.faces[1].area
			  e11.faces[0].erase!
			else
			  e11.faces[1].erase!
			end
		  end
		###
		else ### Y & Z
		###
	      p0=mpt.offset([0, -((@thick1/2.0)+(@tol/2.0)), 0])
		  p1=p0.offset([0, @thick1+@tol, 0])
		  ### move p0/p1 by bit/2
		  if @bit>0
		    vp=[@bit/2.0, 0, 0]
			p0.offset!(vp)
			p1.offset!(vp)
		  end
		  ga=gents.to_a
		  ln=gents.add_line(p0, p1) ### slot end
		  ray=@model.raytest([p0, [-1, 0, 0]])
		  if ray
		    p00=ray[0]
		  else
		    p00=p0.offset([-@diag, 0, 0])
		  end
		  e00=gents.add_line(p0, p00)
		  ray=@model.raytest([p1, [-1, 0, 0]])
		  if ray
		    p11=ray[0]
		  else
		    p11=p1.offset([-@diag, 0, 0])
		  end
		  e11=gents.add_line(p1, p11)
		  gents.intersect_with(true, tr0, gents, tr0, true, ga)
		  ln.find_faces if ln.valid?
		  if e00 && e00.valid? && e00.faces[1]
			if e00.faces[0].area<e00.faces[1].area
			  e00.faces[0].erase!
			else
			  e00.faces[1].erase!
			end
		  end
		  if e11 && e11.valid? && e11.faces[1]
			if e11.faces[0].area<e11.faces[1].area
			  e11.faces[0].erase!
			else
			  e11.faces[1].erase!
			end
		  end
		  ###
		end
	  end
	}
	###
	self.delete_unwanted_edges(gents)
	###
  }
  ###
end#slot2

##############################################
def self.export(rnd=nil, flat1=nil, flat2=nil)
  @msg=S5+": "
  @model=Sketchup.active_model
  @ents=@model.entities
  @defns=@model.definitions
  @ss=@model.selection
  @rnd=rnd
  @flat1=flat1
  @flat2=flat2
  ### sort out rnd/flat1/flat2
  @sep=false
  @ex=""
  unless @rnd ### it's done separately
    @sep=true
  	@ex=" "+EXPR
    @msg=S5+@ex+": "
	Sketchup.status_text=@msg
	### get flat1/2 and rnd from ss
	if @ss.empty?
      UI.messagebox(@msg+"\n\n"+NOS)
      return nil
    end#if
    if @ss[1]
      UI.messagebox(@msg+"\n\n"+SOFLAT)
      return nil
    end#if
    unless @ss[0].is_a?(Sketchup::Group) && (@ss[0].name =~ /^FLAT/ && @ss[0].name =~ /[-][0-9][0-9][0-9][0-9]$/)
      UI.messagebox(@msg+"\n\n"+(@ss[0].to_s)+"\n\n"+SNOTF)
      return nil
    end#if
    @flat1=@ss[0]
	@ss.clear
	f1name=@flat1.name
	###
	nam=f1name.split("-")[0]
	@rnd=f1name.split("-")[1]
	if nam=~/[2]$/
		f2name = f1name.gsub(/[2][-]/,"-")
	else
		f2name = f1name.gsub(/[-]/,"2-")
	end
	###
	@flat2=nil
	@defns.each{|d|
	  next unless d.group?
	  if d.instances[0].name==f2name
	    @flat2 = d.instances[0]
		break
	  end
	}
  end
  ### Get matching SLICES and Audit
  slices=nil
  @defns.each{|d|
    next unless d.group?
    if d.instances[0].name==f1name.gsub(/FLAT/, "SLICES")
	  slices=d.instances[0]
	  break
	end
  }
  #@ss.add(slices) if slices
  #if slices && TIG::Slicer.audit(false)==false
    #@ss.clear
    #return nil
  #end
  ### done Audit
  @ss.clear
  @ex=" "+EXPR
  @msg=S5+@ex+": "
  ###
  UI.beep
  ans=UI.messagebox(@msg+"\n\n"+QPNG, MB_YESNO,"")
  if ans==6
    Sketchup.status_text=@msg+"PNG..."
	TIG::Slicer.pngs(@rnd)
  end
  Sketchup.status_text=@msg
  if Sketchup.is_pro?
	UI.beep ### DXF
	ans=UI.messagebox(@msg+"\n\n"+QDXF, MB_YESNO,"")
	if ans==6
	  Sketchup.status_text=@msg+"DXF..."
	  TIG::Slicer.dxfs(@rnd)
	end
  end
  Sketchup.status_text=@msg+DONE
  ###
end#export

def self.pngs(rnd='')
  @rnd=rnd
  ###
  imgfolder=File.join(File.dirname(@model.path), @model.title+"-Slicer5-PNGs-"+@rnd).tr("\\","/")
  begin
	Dir.mkdir(imgfolder)
  rescue
  end
  ###
  	if @flat2
		tot1=@flat1.entities.length.to_s
		tot2=@flat2.entities.length.to_s
		UI.messagebox(@msg+"\n\n"+tot1+"nr "+@flat1.name+" & "+tot2+"nr "+@flat2.name+" "+PNGS+"\n\n"+imgfolder)
	else
		tot1=@flat1.entities.length.to_s
		UI.messagebox(@msg+"\n\n"+tot1+"nr "+@flat1.name+" "+PNGS+"\n\n"+imgfolder)
	end
  ###
  @view=@model.active_view
  cam=@view.camera
  @cam=cam
  @came=cam.eye
  @camt=cam.target
  @camu=cam.up
  @camp=cam.perspective? rescue @camp=false
  ### start and abort at end
 @model.start_operation(@msg+" PNGs", true)
  ### ensure cpoints show etc
  hgc=@model.rendering_options["HideConstructionGeometry"]
  @model.rendering_options["HideConstructionGeometry"]=false
  ###
  hid=@model.rendering_options["DrawHidden"]
  @model.rendering_options["DrawHidden"]=false ###
  ###
  ds=@model.shadow_info["DisplayShadows"]
  @model.shadow_info["DisplayShadows"]=false
  ###
  camtop=Sketchup::Camera.new([0, 0, 1], [0, 0, 0], Y_AXIS)
  camtop.perspective=false
  @view.camera=camtop
  #@view.refresh
  ###
  ### hide all
  all = @model.entities.to_a + @model.active_entities.to_a - [@flat1]
  all.uniq!
  all.dup.each{|e| all.delete(e) if e.hidden? }
  all.each{|e| e.hidden=true }
  ### do flat1
  ints=@flat1.entities.to_a
  ### find max size MUST include flat2 too
  mwidth=0
  mheight=0
  if @flat2
    fints = ints + @flat2.entities.to_a 
  else
    fints = ints.dup
  end
  fints.grep(Sketchup::ComponentInstance).each{|ins|
    ins.definition.invalidate_bounds
    bb = ins.bounds
    mwidth  = bb.width if bb.width   > mwidth
    mheight = bb.height if bb.height > mheight
  }
  vpw=2*@view.vpwidth
  vph=2*@view.vpheight
  tot=ints.length.to_s
  ints.grep(Sketchup::ComponentInstance).each{|ins|
    ###
    ints.each{|e| e.hidden=false }
    ins.definition.invalidate_bounds
    imgname=ins.definition.name+".png"
	Sketchup.status_text=@msg+imgname
    imgpath=File.join(imgfolder,imgname).tr("\\","/")
    ints.each{|e| e.hidden=true unless e==ins }
    bb=ins.bounds
    ctr=bb.center
    pmin=ctr.offset([-mwidth/2.0, -mheight/2.0, 0])
    pmax=ctr.offset([mwidth/2.0, mheight/2.0, 0])
    cpts=[]
	cpts << @flat1.entities.add_cpoint(ctr)
    cpts << @flat1.entities.add_cpoint(pmin)
    cpts << @flat1.entities.add_cpoint(pmax)
    camtop.set([ctr.x, ctr.y, ctr.z+1], ctr, Y_AXIS)
	@view.zoom_extents
	#@view.refresh
	@flat1.entities.erase_entities(cpts)
    keys={
        :filename => imgpath,
        :width => vpw,
        :height => vph,
        :antialias => false,
        :compression => 0.9
    }
    @model.active_view.write_image(keys)
	###
  }
  ###
  ints.each{|e| e.hidden=false }
  ### unhide
  all.each{|e| e.hidden=false }
  ### do flat2
 if @flat2
   ###
   #@model.start_operation(@msg+" PNGs", true)
   ### hide all
  all=@model.entities.to_a+@model.active_entities.to_a-[@flat2]
  all.uniq!
  all.dup.each{|e| all.delete(e)if e.hidden? }
  all.each{|e| e.hidden=true }
  ###
  ints=@flat2.entities.to_a
  ### USE max size from earlier !
  vpw=2*@view.vpwidth
  vph=2*@view.vpheight
  tot=ints.length.to_s
  ints.grep(Sketchup::ComponentInstance).each{|ins|
    ###
	ints.each{|e| e.hidden=false }
    imgname=ins.definition.name+".png"
	Sketchup.status_text=@msg+imgname
    imgpath=File.join(imgfolder,imgname).tr("\\","/")
    ints.each{|e| e.hidden=true unless e==ins }
    bb=ins.bounds
    ctr=bb.center
    pmin=ctr.offset([-mwidth/2.0, -mheight/2.0, 0])
    pmax=ctr.offset([mwidth/2.0, mheight/2.0, 0])
	cpts=[]
	cpts << @flat2.entities.add_cpoint(ctr)
    cpts << @flat2.entities.add_cpoint(pmin)
    cpts << @flat2.entities.add_cpoint(pmax)
    camtop.set([ctr.x, ctr.y, ctr.z+1], ctr, Y_AXIS)
	@view.zoom_extents
	#@view.refresh
	@flat2.entities.erase_entities(cpts)
    keys={
        :filename => imgpath,
        :width => vpw,
        :height => vph,
        :antialias => false,
        :compression => 0.9
    }
    @model.active_view.write_image(keys)
	###
  }
  ints.each{|e| e.hidden=false }
  ### unhide
  all.each{|e| e.hidden=false }
  ###
 @model.abort_operation ### undo all changes from start
  ###
 end#if
  ###
  @model.rendering_options["HideConstructionGeometry"]=hgc
  @model.rendering_options["DrawHidden"]=hid
  @model.shadow_info["DisplayShadows"]=ds
  ###
  ### reset camera as it was
  camo=Sketchup::Camera.new(@came, @camt, @camu)
  @view.camera=camo
  @view.camera.perspective=@camp
  @view.refresh
  ###
end#pngs


def self.dxfs(rnd='')
  @rnd=rnd
  ###
  dxffolder=File.join(File.dirname(@model.path), @model.title+"-Slicer5-DXFs-"+@rnd).tr("\\","/")
  begin
	Dir.mkdir(dxffolder)
  rescue
  end
  @defns=@model.definitions
  @ents=@model.entities
  @layers=@model.layers
  @view=@model.active_view
  cam=@view.camera
  @cam=cam
  @came=cam.eye
  @camt=cam.target
  @camu=cam.up
  @camp=cam.perspective? rescue @camp=false
  ###
  names=[]
  msg=@msg
  @defns.each{|defn|
    next unless defn.name=~/^#{@rnd}[-][XYZ][0-9][0-9][0-9]/
    names << defn.name if defn.instances[1] ### only export used panels
    msg=msg+"."
    Sketchup.status_text=msg
  }
  names.sort!
  return nil unless names[0]
  ###
  @model.commit_operation unless @sep ### as it's nested 
  @dxf=true
  ###
  Sketchup.status_text=@msg+"DXFs "
  ###
  camtop=Sketchup::Camera.new([0,0,1], [0,0,0], Y_AXIS)
  camtop.perspective=false
  @view.camera=camtop
  @view.refresh
  dxfs=[]
  ###
  names.each{|name| ####################################################
    ###
	@model.start_operation(@msg+"DXFs", true)
	###
	defn=@defns[name]
    ###
    dxf=File.join(dxffolder, name+'.dxf').tr("\\","/")
    ###
    Sketchup.status_text=(@msg+"DXF "+name+'.dxf')
    ### sort out 2D layers
    layerE=@layers.add("EXTL")
    layerI=@layers.add("INTL")
    layerT=@layers.add("TEXT")
    layerO=@layers.add("OLIN")
    layerC=@layers.add("CIRC")
    ###
    ### erase ALL
    @ents.erase_entities(@ents.to_a)
    ### add defn
    tr=Geom::Transformation.new()
    inst=@ents.add_instance(defn, tr)
    ###
    ### use Slice geom [but leave Text/Outline as group]
	  ### relocate Text/Outline at z=0
	  txt=nil
	  oli=nil
	  defn.entities.grep(Sketchup::Group).each{|e|
		if e.name=="TEXT"
          txt=e
		elsif e.name=="OLIN"
		  oli=e
		end
      }
	  ###
	  if txt
	    pt=txt.bounds.min
	    pt.z=0
	    tr=Geom::Transformation.translation(txt.bounds.min.vector_to(pt))
	    txt.transform!(tr)
	  end
	  ###
	  if oli
	    pt=oli.bounds.min
	    pt.z=0
	    tr=Geom::Transformation.translation(oli.bounds.min.vector_to(pt))
	    oli.transform!(tr)
	  end
	  ### geom - keep only bottom face
      togos=[]
      defn.entities.grep(Sketchup::Edge).each{|e|
        togos << e unless e.bounds.center.z==0
      }
      togos.uniq!
      defn.entities.erase_entities(togos)
	  ###
	  faces=defn.entities.grep(Sketchup::Face)
	 faces.each{|face|
      ### EXTL outer loop
	  oloop=face.outer_loop
	  oedges=oloop.edges
	  overts=oloop.vertices
	  overts << overts[0]
	  opts=[]
	  overts.each{|v| opts << v.position }
	  ### INTL inner loops [if any]
	  loops=face.loops-[oloop]
	  iverts=[]
	  circs=[]
	  ###
	  loops.each{|loop|
	    e=loop.edges[0]
		if e.curve && e.curve.typename=="ArcCurve" && e.curve.vertices[0].position==e.curve.vertices[-1].position && e.curve.radius <= 6.mm
		  ### we only 'drill' small circles diam<=12mm
		  circs << [e.curve.center.to_a, e.curve.radius]
        else ### it's an internal hole
	      iverts << loop.vertices+[loop.vertices[0]]
		end
	  }### make outer loop from opts
	  edges=@ents.add_curve(opts)
	  edges.each{|ed| ed.layer=layerE }
	  ###
	  ### make any circle holes
	  circs.uniq!
	  circs.each{|c|
	    edges=@ents.add_circle(c[0], Z_AXIS.reverse, c[1], 24)
	    edges.each{|ed| ed.layer=layerC }
	  }
	  ### make other inner loops
	  ipts=[]
	  iverts.each{|vs|
	    pts=[]
	    vs.each{|v| pts << v.position }
		ipts << pts
      }
	  ipts.each{|pts|
	    edges=@ents.add_curve(pts)
	    edges.each{|ed| ed.layer=layerI }
	  }
	  ###
	 }#faces
	 ###
	if txt ### sort out Text ===========================
	 tt=txt.transformation
	 int=@ents.add_instance(txt.entities.parent, tt)
	 txt.erase!
	 ### polyline text's edges
	 tents=int.entities
	 edges=tents.to_a
	 cedges=[]
	 edges.each{|e| cedges << e.all_connected }
	 cedges.each{|edgs|
	   next unless edgs[0].valid?
	   next unless edgs[0].curve
	   vs=[]
	   edgs.each{|e| vs<<e.vertices }
	   vs.flatten!
	   vs.uniq!
	   vs << vs[0]
	   pts=[]
	   vs.each{|v| pts << v.position }
	   tents.erase_entities(edgs)
	   tents.add_curve(pts)
	 }
	 tents.each{|e| e.layer=layerT }
	 int.explode
	 ###
	end#txt
	###
	if oli ### sort out Outline ===========================
	 tt=oli.transformation
	 int=@ents.add_instance(oli.entities.parent, tt)
	 oli.erase!
	 ### polyline oli's edges
	 tents=int.entities
	 edges=tents.to_a
	 cedges=[]
	 edges.each{|e|
	   stop=false
	   cedges.each{|cs|
	     if cs.include?(e)
		   stop=true
		   break
		 end
	   }
	   next if stop
	   cedges << e.all_connected
	 }
	 vss=[]
	 cedges.each{|edgs|
	   vs=[]
	   edgs.each{|e| vs<<e.vertices }
	   vs.flatten!
	   vs.uniq!
	   ### see if looped
	   if edgs.length>=vs.length
	   ### looped OR edgs<vs == meet at corner '8' etc
	     faces=tents.grep(Sketchup::Face)
		 edgs.each{|e| e.find_faces unless e.faces[0] }
		 nfaces=tents.grep(Sketchup::Face)
		 nfaces=nfaces-faces
		 nfaces.each{|f|
		   olvs=f.outer_loop.vertices
		   olvs << olvs[0]
		   olps=[]
		   olvs.each{|v| olps << v.position }
		   vss << olps
		 }
	   else ### edgs.length<vs.length ### NOT looped
	     ### so we order verts ?
		 v0=nil
		 vs.each{|v|
		   if v.edges.length==1
		     v0=v
			 break
		   end
		 }
		 xvs=[v0]
		 e0=v0.edges[0]
		 ess=[e0]
		 (vs.length-1).times{
		   v0=e0.other_vertex(v0)
		   xvs << v0
		   v0.edges.each{|e|
		     unless ess.include?(e)
			   e0=e
			   ess << e
			 end
		   }
		 }
		 xps=[]
		 xvs.each{|v| xps << v.position }
		 vss << xps
		 ###
	   end
	   vss.each{|pts|
	     edgs.each{|e| e.erase! if e.valid? }
	     tents.add_curve(pts)
	   }
	   tents.grep(Sketchup::Face).each{|e| e.erase! if e.valid? }
	 }#edgs
	 tents.each{|e| e.layer=layerO }
	 int.explode
	end#oli
    ### adjust so slice's bb.min is at origin [0,0,0]
    min=inst.bounds.min
	vec=min.vector_to(ORIGIN)
    trv=Geom::Transformation.translation(vec)
	inst.erase!
	@ents.transform_entities(trv,@ents.to_a)
	### flatten!
	pt0=@ents[0].start.position
	pt1=pt0.clone
	pt1.z=0
	vvv=pt0.vector_to(pt1)
	tvv=Geom::Transformation.translation(vvv)
	@ents.transform_entities(tvv, @ents.to_a)
    ###
    ### purge all unused compos and layers
    @defns.purge_unused
    @layers.purge_unused
    ###
    ### zoom to plan/top and extents
    @view.zoom_extents
    @view.refresh
    ###
    @model.export(dxf)
	###
	dxfs << dxf
    ###
    begin
      @model.abort_operation ### undo so back as it was !!!
    rescue Exception => e
      puts e
    end
    ###
  } ####################################################################
  ###
  dxfs.each{|dxf| TIG::Slicer.recolorlayers(dxf) }
  ###
  ### reset camera as it was
  @view.camera.perspective=@camp
  camo=Sketchup::Camera.new(@came, @camt, @camu)
  @view.camera=camo
  ###
  Sketchup.status_text=(@msg+" "+DXFS)
  UI.messagebox(@msg+"\n\n"+names.length.to_s+" "+DXFX+"\n\n"+dxffolder)
  #UI.openURL('file:///'+dxffolder)
  ###
end#dxfs

def self.recolorlayers(dxf)
=begin
0 		7 white/black
EXTL	2 yellow
INTL	3 green
TEXT	1 red
OLIN    6 magenta
CIRC    7 white/black
=end
  text=IO.read(dxf)
  ### make DOS > MAC; CR+LF > LF; return later
  pc=false
  pc=true if RUBY_PLATFORM =~ /mswin|mingw/
  unless pc
    text.gsub!(/\r\n/,"\n")
  end
  ###
  bits=text.split("TABLE\n  2\nLAYER\n")
  start=bits[0]+"TABLE\n  2\nLAYER\n"
  rest=bits[1]
  table=rest.split("ENDTAB\n")[0]
  remainder=rest.split("ENDTAB\n")[1..-1].join("ENDTAB\n")
  layers=table.split("LAYER\n")
  dxfout=dxf ###+".DXF" ###
  file=File.open(dxfout,"w")
  file.puts(start)
  file.puts(layers[0])
  layers[1..-1].compact.each{|layer|
    next unless layer
    layer="LAYER\n"+layer
    parts=layer.split("  2\n")
	next unless parts[1]
	parts[0]=parts[0]+"  2\n"
	name=parts[1].split("\n")[0]
	case name
	when "0"
	  parts[1]=parts[1].gsub(/420\n[ 0-9]+\n/,"").gsub(/ 62\n[ 0-9]+\n/," 62\n     7\n")
	when "EXTL"
	  parts[1]=parts[1].gsub(/420\n[ 0-9]+\n/,"").gsub(/ 62\n[ 0-9]+\n/," 62\n     2\n")
	when "INTL"
	  parts[1]=parts[1].gsub(/420\n[ 0-9]+\n/,"").gsub(/ 62\n[ 0-9]+\n/," 62\n     3\n")
	when "TEXT"
	  parts[1]=parts[1].gsub(/420\n[ 0-9]+\n/,"").gsub(/ 62\n[ 0-9]+\n/," 62\n     1\n")
	when "OLIN"
	  parts[1]=parts[1].gsub(/420\n[ 0-9]+\n/,"").gsub(/ 62\n[ 0-9]+\n/," 62\n     6\n")
	when "CIRC"
	  parts[1]=parts[1].gsub(/420\n[ 0-9]+\n/,"").gsub(/ 62\n[ 0-9]+\n/," 62\n     7\n")
	else ### ???
	  parts[1]=parts[1].gsub(/420\n[ 0-9]+\n/,"").gsub(/ 62\n[ 0-9]+\n/," 62\n     7\n")
	end
	file.puts(parts[0])
	file.puts(parts[1])
  }
  file.puts("ENDTAB\n")
  file.puts(remainder)
  file.close
  ### return MAC > DOS;  LF > CR+LF 
  unless pc
    text=IO.read(dxf)
    text.gsub!(/\n/,"\r\n")
	file=File.open(dxfout,"w")
    file.puts(text)
	file.close
  end
  ###
end#recolorlayers


########################################################################
def self.slotter()
  ###
  @model=Sketchup.active_model
  @ents=@model.entities
  @defns=@model.definitions
  @ss=@model.selection
  @rnd=nil
  @set1=nil
  @set2=nil
   @ex=" "+SR
   @msg=S5+@ex+": "
	### get set1/2 and rnd from ss
	if @ss.empty?
      UI.messagebox(@msg+"\n\n"+NOS)
      return nil
    end#if
    if @ss[1]
      UI.messagebox(@msg+"\n\n"+SSG)
      return nil
    end#if
    unless @ss[0].is_a?(Sketchup::Group) && @ss[0].name =~ /^SLICES/ && @ss[0].name =~ /[-][0-9][0-9][0-9][0-9]$/
      UI.messagebox(@msg+"\n\n"+SNOTS)
      return nil
    end#if
    @set1 = @ss[0]
	s1name = @set1.name
	if s1name =~ /[2][-]/
		s2name = s1name.gsub(/[2][-]/,"-")
	else
		s2name = s1name.gsub(/[-]/,"2-")
	end
	@set2=nil
	@defns.each{|d|
	  next unless d.group?
	  if d.instances[0].name==s2name
	    @set2=d.instances[0]
		break
	  end
	}
	unless @set2
	  UI.messagebox(@msg+"\n\n"+SGRP+" '"+s1name+"' "+NOMA+" '"+s2name+"' "+GRP+"\n"+SONOS)
      return nil
	end#if
   ###
  @rnd=s1name.split("-")[1]
  ###
  @slices1=@set1.entities.to_a ### ==instances
  @slices2=@set2.entities.to_a
  @type1=nil
  @slices1.grep(Sketchup::ComponentInstance).each{|e|
	@type1=e.definition.name
	break
  }
  @type2=nil
  @slices2.grep(Sketchup::ComponentInstance).each{|e|
    @type2=e.definition.name
	break
  }
  ### get thicknesses
	@thicknesses1=[]
	@slices1.grep(Sketchup::ComponentInstance).each{|slice|
	  thk=@x0
	  slice.definition.entities.grep(Sketchup::Edge).each{|e|
	    if e.line[1].parallel?(Z_AXIS)
		  thk=e.length
		  break
		end
	  }
	  @thicknesses1 << thk
	}
	fail=false
	@thicknesses1.each{|e|
	  if e==@x0
	    fail=true
		break
	  end
	}
	if fail
	  UI.messagebox(@msg+"\n\n"+SGRP+" '"+s1name+"' "+ZERO+"\n"+SONOS)
      return nil
	end
	###
	@thicknesses2=[]
	@slices2.grep(Sketchup::ComponentInstance).each{|slice|
	  thk=@x0
	  slice.definition.entities.grep(Sketchup::Edge).each{|e|
	    if e.line[1].parallel?(Z_AXIS)
		  thk=e.length
		  break
		end
	  }
	  @thicknesses2 << thk
	}
	fail=false
	@thicknesses2.each{|e|
	  if e==@x0
	    fail=true
		break
	  end
	}
	if fail
	  UI.messagebox(@msg+"\n\n"+SGRP+" '"+s2name+"' "+ZERO+"\n"+SONOS)
      return nil
	end
	###
	return nil unless self.dialogSLOT()
	### 
    @model.start_operation(@msg, true)
    @ss.clear
    ### slimming
	self.slimmer(@slices1, @thicknesses1)
	self.slimmer(@slices2, @thicknesses2)
	### intersector + slotter
	self.intersector1() ### slotter1
	self.intersector2() ### slotter2
	### thickening
	self.thickener(@slices1, @thicknesses1)
	self.thickener(@slices2, @thicknesses2)
	###
  ###
  @model.commit_operation
  ###
end#slotter

def self.dialogSLOT()
  Sketchup.status_text=(@msg+PARA)
  @tol=@@tol
  @tol=@x0 unless @tol
  @bit=@@bit
  @bit=@x0 unless @bit
  prompts=[TOL+": ", OCUT+": "]
  values=[@tol,@bit]
  pops=["",""]
  title=@msg+PARA
  results=inputbox(prompts, values, pops, title)
  return nil unless results
  @tol,@bit = results
  @@tol,@@bit = @tol,@bit
  return true
end#dialogSLOT

def self.slimmer(slices=[], thicknesses=[])
  Sketchup.status_text=(@msg+SMING+"...")
  ### move top geom to z=mid-point
  slices.grep(Sketchup::ComponentInstance).each_with_index{|slice, i|
    thk=thicknesses[i]
    tr=Geom::Transformation.translation([0, 0, -thk/2.0])
	slice.definition.entities.transform_entities(tr, slice.definition.entities.to_a)
	self.text_hider(slice.definition.entities)
	slice.definition.entities.grep(Sketchup::Edge).each{|e|
	  e.erase! if e.bounds.center.z<=0
	}### now only top geom at z=thk/2
  }
  ###
end#slimmer

def self.thickener(slices=[], thicknesses=[])
  Sketchup.status_text=(@msg+THING+"...")
  slices.grep(Sketchup::ComponentInstance).each_with_index{|slice, i|
    thk=thicknesses[i]
    faces=slice.definition.entities.grep(Sketchup::Face)
	faces.each{|face|face.pushpull(-thk)}
	tr=Geom::Transformation.translation([0, 0, thk/2.0])
	self.text_unhider(slice.definition.entities)
	slice.definition.entities.transform_entities(tr, slice.definition.entities.to_a)
	self.delete_unwanted_edges_slotter(slice.definition.entities)
  }
  ###
end#thickener


def self.text_hider(ents) ### needed to avoid intersect_with issues !
  ents.grep(Sketchup::Group).each{|e| e.hidden=true if e.name=="TEXT" }
end#text_hider
def self.text_unhider(ents)
  ents.grep(Sketchup::Group).each{|e| e.hidden=false if e.name=="TEXT" }
end#text_unhider

def self.intersector1()
  Sketchup.status_text=(@msg+INING+" 1...")
  ###
  ts1=@set1.transformation
  ts2=@set2.transformation
  ### change set2 origin to match set1
  or1=ts1.origin
  or2=ts2.origin
  v12=or1.vector_to(or2)
  tvv=Geom::Transformation.translation(v12)
  @set2.entities.transform_entities(tvv, @set2.entities.to_a)
  tvv=Geom::Transformation.translation(v12.reverse)
  @set2.transform!(tvv)
  ###
  @slices1.grep(Sketchup::ComponentInstance).each{|slice1|
    ents=slice1.definition.entities
	t1=slice1.transformation
	@slices2.grep(Sketchup::ComponentInstance).each_with_index{|slice2, i|
	  nedges=ents.intersect_with(true, t1, ents, t1, false, slice2)
      self.slotter1(slice1, ents, nedges, i)
	}
  }
  ### set it back!
  @set2.entities.parent.invalidate_bounds
  ###
end#intersector1

def self.slotter1(slice=nil, gents=[], nedges=[], i=0)
  ###got slice/gents/nedges/i - get mpts
  tss=slice.transformation
  tr0=Geom::Transformation.new()
  thk=@thicknesses2[i]
  diag=slice.bounds.diagonal
  ###
   vline=X_AXIS.clone
   mpts=[]
   nedges.each{|e|
     next unless e.valid?
	 p0=e.start.position
	 p1=e.end.position
     mpts << p0.offset(p0.vector_to(p1), e.length/2.0)
	if @type2 =~ /^[0-9][0-9][0-9][0-9]-Z/
	 ###
	 if p0.y<p1.y
	   vline=e.line[1].reverse ### center or back 1/2
	   #p 100
	   if e.start.edges[1] && ! e.end.edges[1] ### front 1/2
		 #p 0
	   elsif e.end.edges[1] && ! e.start.edges[1]
		 #p 10
		 vline.reverse!
	   end
	 elsif p0.y>=p1.y
	   #p 111
	   vline=e.line[1] ### center or back 1/2
	   if e.end.edges[1] && ! e.start.edges[1] ### front 1/2
		 #p 1
	   elsif e.start.edges[1] && ! e.end.edges[1]
         #p 11
		 vline.reverse!
	   end
	 end
	 ###
	else ### it's Y axis 
	 ###
	 if p0.y<p1.y
	   vline=e.line[1].reverse ### center or back 1/2
	   if ! e.start.edges[1] && e.end.edges[1] ### front 1/2
	     vline.reverse!
	   end
	 elsif p0.y>=p1.y
	   vline=e.line[1] ### center or back 1/2
	   if ! e.end.edges[1] && e.start.edges[1] ### front 1/2
	     vline.reverse!
	   end
	 end
	 ###
	end#type2
	 ###
	 e.erase!
	 ###
   }
   ###
   mpts.each{|mpt|
		###
		tr=Geom::Transformation.rotation(mpt, Z_AXIS, 90.degrees)
		ve=vline.clone.transform(tr)
		p0=mpt.offset(ve, (thk/2.0)+(@tol/2.0))
		p1=p0.offset(ve.reverse, thk+@tol)
		if @bit>0
	      p0.offset!(vline.reverse, @bit/2.0)
		  p1.offset!(vline.reverse, @bit/2.0)
		end
		###
		ga=gents.to_a
		 ###
		  ln=gents.add_line(p0, p1) ### slot end
		  p00=p0.offset(vline, diag)
		  e00=gents.add_line(p0, p00)
		  p11=p1.offset(vline, diag)
		  e11=gents.add_line(p1, p11)
		  gents.intersect_with(true, tr0, gents, tr0, false, ga)
		  ln.find_faces if ln.valid?
		  gents.intersect_with(true, tr0, gents, tr0, false, gents.to_a)
		  if e00.valid? && e00.faces[1]
			if e00.faces[0].area<e00.faces[1].area
			  e00.faces[0].erase!
			else
			  e00.faces[1].erase!
			end
		  end
		  if e11.valid? && e11.faces[1]
			if e11.faces[0].area<e11.faces[1].area
			  e11.faces[0].erase!
			else
			  e11.faces[1].erase!
			end
		  end
		  ###
		  self.delete_unwanted_edges_slotter(gents)
	###
	}#mpts
	###
end#slotter1
###
def self.intersector2()
  Sketchup.status_text=(@msg+INING+" 2...")
  ###
  ts1=@set1.transformation
  ts2=@set2.transformation
  ### change set2 origin to match set1
  or1=ts1.origin
  or2=ts2.origin
  v12=or1.vector_to(or2)
  tvv=Geom::Transformation.translation(v12)
  @set2.entities.transform_entities(tvv, @set2.entities.to_a)
  tvv=Geom::Transformation.translation(v12.reverse)
  @set2.transform!(tvv)
  ###
  @slices2.grep(Sketchup::ComponentInstance).each{|slice2|
    ents=slice2.definition.entities
	t2=slice2.transformation
	@slices1.grep(Sketchup::ComponentInstance).each_with_index{|slice1, i|
	  nedges=ents.intersect_with(true, t2, ents, t2, false, slice1)
      self.slotter2(slice2, ents, nedges, i)
	}
  }
  ### set it back!
  @set2.entities.parent.invalidate_bounds
  ###
end#intersector2

def self.slotter2(slice=nil, gents=[], nedges=[], i=0)
  ###got slice/gents/nedges/i - get mpts
  tss=slice.transformation
  tr0=Geom::Transformation.new()
  thk=@thicknesses1[i]
  diag=slice.bounds.diagonal
  ###
   vline=X_AXIS.clone
   mpts=[]
   nedges.each{|e|
     next unless e.valid?
	 p0=e.start.position
	 p1=e.end.position
	 ###
	if @type2 =~ /^[0-9][0-9][0-9][0-9]-Z/
	 if p0.y<p1.y
	   #p 100
	   mpts << p0
	   vline=e.line[1].reverse ### center or back 1/2
	   if e.start.edges[1] && ! e.end.edges[1] ### front 1/2
		 mpts[-1]=p1
	     vline.reverse!
		 #p 0
	   end
	   if ! e.start.edges[1] && e.end.edges[1]
		 #p 10
	   end
	 elsif p0.y>=p1.y
	   #p 111
	   mpts << p1
	   vline=e.line[1] ### center or back 1/2
	   if e.end.edges[1] && ! e.start.edges[1] ### front 1/2
		 mpts[-1]=p0
	     vline.reverse!
		 #p 1
	   end
	   if e.start.edges[1] && ! e.end.edges[1]
		 #p 11
	   end
	 end
	 ###
	else ### it's Y axis 
	 ###
	 if p0.y<p1.y
	   mpts << p1
	   vline=e.line[1].reverse ### center or back 1/2
	   if e.start.edges[1] && ! e.end.edges[1] ### front 1/2
	     vline.reverse!
	   elsif e.end.edges[1] && ! e.start.edges[1]
		 mpts[-1]=p0
	   end
	 elsif p0.y>=p1.y
	   mpts << p0
	   vline=e.line[1] ### center or back 1/2
	   if e.end.edges[1] && ! e.start.edges[1] ### front 1/2
	     vline.reverse!
	   elsif e.start.edges[1] && ! e.end.edges[1]
		 mpts[-1]=p1
	   end
	 end
	 ###
	end#type2
	 ###
	 vline.reverse! ### must be opposite to other silices !
	 ###
	 e.erase!
	 ###
   }
   ###
   mpts.each{|mpt|
		###
		tr=Geom::Transformation.rotation(mpt, Z_AXIS, 90.degrees)
		ve=vline.clone.transform(tr)
		p0=mpt.offset(ve, (thk/2.0)+(@tol/2.0))
		p1=p0.offset(ve.reverse, thk+@tol)
		if @bit>0
	      p0.offset!(vline.reverse, @bit/2.0)
		  p1.offset!(vline.reverse, @bit/2.0)
		end
		###
		ga=gents.to_a
		 ###
		  ln=gents.add_line(p0, p1) ### slot end
		  p00=p0.offset(vline, diag)
		  e00=gents.add_line(p0, p00)
		  p11=p1.offset(vline, diag)
		  e11=gents.add_line(p1, p11)
		  gents.intersect_with(true, tr0, gents, tr0, false, ga)
		  ln.find_faces if ln.valid?
		  gents.intersect_with(true, tr0, gents, tr0, false, gents.to_a)
		  if e00.valid? && e00.faces[1]
			if e00.faces[0].area<e00.faces[1].area
			  e00.faces[0].erase!
			else
			  e00.faces[1].erase!
			end
		  end
		  if e11.valid? && e11.faces[1]
			if e11.faces[0].area<e11.faces[1].area
			  e11.faces[0].erase!
			else
			  e11.faces[1].erase!
			end
		  end
		  ###
		  self.delete_unwanted_edges_slotter(gents)
	###
	}#mpts
	###
end#slotter2
###

def delete_spurious_edges(ents)
  ###
  edges=ents.grep(Sketchup::Edge)-@eds
  return unless edges[0]
  ###
  if @selected.is_a?(Sketchup::Group)
	sents=@selected.entities
  else ### ComponentInstance
	sents=@selected.definition.entities
  end
  faces = sents.grep(Sketchup::Face)
  ###
  t1=@selected.transformation.inverse
  t2=Geom::Transformation.new() #edges[0].parent.instances[0].parent.instances[0].transformation
  t3=edges[0].parent.instances[0].transformation
  tr=(t3*t2*t1)
  ###
  togos=[]
  ###
  edges.each{|e|
    fq=false
	pt=e.start.position
	pt.transform!(tr)
	faces.each{|face|
		cp=face.classify_point(pt)
		if cp==Sketchup::Face::PointOnVertex || cp==Sketchup::Face::PointOnEdge #|| Sketchup::Face::PointInside
			fq=true
			break
		end
	}
	###
	fqq=false
	pt=e.end.position
	pt.transform!(tr)
	faces.each{|face|
		cp=face.classify_point(pt)
		if cp==Sketchup::Face::PointOnVertex || cp==Sketchup::Face::PointOnEdge #|| Sketchup::Face::PointInside
			fqq=true
			break
		end
	}
	togos << e unless fq && fqq
  }
  ents.erase_entities(togos) if togos[0]
  ###
end
###
###
def self.delete_unwanted_edges_slotter(ents)
  ###
  edges=ents.grep(Sketchup::Edge)
  ###
  togos=[]
  edges.each{|e|
    next unless e.valid?
    if e.faces.length==0
      togos << e ### lonely
	elsif e.faces.length==2 && e.faces[0].normal.dot(e.faces[1].normal) > 0.99999999
	  togos << e ### faced but coplanar
    end
  }
  ents.erase_entities(togos)
  ###
end
###
def delete_unwanted_edges(ents)
  ###
  edges=ents.grep(Sketchup::Edge)
  ###
  togos=[]
  edges.each{|e|
    next unless e.valid?
    if e.faces.length==0
      togos << e ### lonely
	elsif e.faces.length==2 && e.faces[0].normal.dot(e.faces[1].normal) > 0.99999999
	  togos << e ### faced but coplanar
    end
  }
  ents.erase_entities(togos)
  ###
end

########################################################################
def self.audit(rep=true)
  #return unless rep
  @msg=S5+": "
  @model=Sketchup.active_model
  @ents=@model.entities
  @defns=@model.definitions
  @ss=@model.selection
  @rnd=nil
  @flat1=nil
  @flat2=nil
  @set1=nil
   @ex=" "+SA
   @msg=S5+@ex+": "
	### get set1/2 and rnd from ss
	if @ss.empty?
      UI.messagebox(@msg+"\n\n"+NOS)
      return nil
    end#if
    if @ss[1]
      UI.messagebox(@msg+"\n\n"+SSG)
      return nil
    end#if
    unless @ss[0].is_a?(Sketchup::Group) && @ss[0].name =~ /^SLICES/ && @ss[0].name =~ /[-][0-9][0-9][0-9][0-9]$/
      UI.messagebox(@msg+"\n\n"+SNOTS)
      return nil
    end#if
    @set1 = @ss[0]
	s1name = @set1.name
	if s1name =~ /[2][-]/
		s2name = s1name.gsub(/[2][-]/,"-")
	else
		s2name = s1name.gsub(/[-]/,"2-")
	end
	@set2=nil
	@defns.each{|d|
	  next unless d.group?
	  if d.instances[0].name==s2name
	    @set2=d.instances[0]
		break
	  end
	}
  ###
  @rnd=s1name.split("-")[1]
  ###
  @text=@msg+"\n"
  @text << "\n"+s1name+"\n"
  ###
  ### make it undoable
 @model.start_operation(@msg,true)
  ###
  ### remove non-instances from SLICES - e.g. clines
  sents=@set1.entities
  togos=[]
  sents.each{|e|
    next if e.is_a?(Sketchup::ComponentInstance)
	togos << e
  }
  @text << VALID+": "
  if togos[0]
    @text << NONER+" "+togos.length.to_s+".\n"
    sents.erase_entities(togos)
  else
	@text << OK+".\n"
  end
  ###
  @slices1=sents.to_a ### ==instances
  @type1=nil
  @slices1.grep(Sketchup::ComponentInstance).each{|e|
    @type1=e.definition.name
	break
  }
  ###
  ### check each slice1 for ref-tag
  names=[]
  @slices1.grep(Sketchup::ComponentInstance).each{|slice|
    names << slice.definition.name if slice.valid?
  }
  names.uniq!
  names.sort!
  ###
  defns=[]; names.each{|n| defns << @defns[n] }
  tags=[]; defns.each{|d|
    tag=nil ### ==NO tag
    d.entities.grep(Sketchup::Group).each{|e|
	  if e.name=="TEXT"
	    tag=e
	    break
	  end
    }
	tags << tag
  }
  ###
  txts=[]
  tags.each{|tag|
    if tag
	  txts << tag.get_attribute("Slicer5", "ref", "?")
	else
	  txts << "?"
	end
  }
  ###
 if tags.compact[0] ### There are some NOT nil tags
   ### fix broken tags, txt==nil it is 'unset'
   @text << TREFT+"...\n"
   names.each_with_index{|name, i|
    if tags[i]==nil ### missing tag
	  @text << name+" "+TMISS+".\n"
	  ents=@defns[name].entities
	  txt=name.split("-")[1..-1].join("-")
	  zz=@x0
	  ents.grep(Sketchup::Edge).each{|e|
	    if e.line[1].parallel?(Z_AXIS)
		  zz=e.length
		  break
		end
	  }
	  height=zz
	  tags.each{|tag|
	    if tag!=nil
		  height=tag.bounds.height
	      height=tag.bounds.width if tag.bounds.width<height
		  @textLayer=tag.layer
		  break
		end
	  }
	  pt=[zz,zz,zz]
	  if @set1.name=~/Y$/
	    rot=0
	  else ### XAR
	    rot=90.degrees
	  end
	  self.text_new(ents, pt, txt, height, rot)
	else ### has tag
	  ents=tags[i].entities
	  txt=name.split("-")[1..-1].join("-")
	  height=tags[i].bounds.height
	  height=tags[i].bounds.width if tags[i].bounds.width<height
	  if txts[i]=="?" ### BUT missing txt
	    @text << name+" "+RATMU+"... "+UPDATED+".\n"
		tags[i].set_attribute("Slicer5", "ref", txt)
		tags[i].make_unique if ents.parent.instances[1]
		self.text_updater(ents, txt, height)
      else ### has txt :)
	    if txts[i]==txt
		  @text << name+" "+TXTOK+".\n"
		else ### shows wrong txt :(
		  @text << name+" "+TXREF+" "+UPDATED+".\n"
		  tags[i].set_attribute("Slicer5", "ref", txt)
		  self.text_updater(ents, txt, height)
		  tags[i].make_unique if ents.parent.instances[1]
		end
	  end
	end
   }
 else ###  NO tags
   @text << TREFT+": "+NONE+".\n"
 end
  ### check for instance match SLICES v FLAT
  flatname = s1name.gsub(/^SLICES/,"FLAT")
  @defns.each{|d|
    next unless d.group?
	  if d.instances[0].name==flatname
	    @flat1=d.instances[0]
		break
	  end
  }
  if @flat1==nil
    @text << "\n"+FLAT+"...\n"+NONE+".\n"
  else ### NO FLAT version
	###
    @text << "\n"+flatname+"\n"
	###
    sents=@flat1.entities
    togos=[]
    sents.each{|e|
      next if e.is_a?(Sketchup::ComponentInstance)
	  togos << e
    }
    @text << VALID+": "
    if togos[0]
      @text << NONER+" "+togos.length.to_s+".\n"
      sents.erase_entities(togos)
    else
	  @text << OK+".\n"
    end
	### check contents
	### compare instances definitions
	sdefs=[]
	@slices1.grep(Sketchup::ComponentInstance).each{|e|
	  sdefs << e.definition
	}
	fdefs=[]
	@flat1.entities.grep(Sketchup::ComponentInstance).each{|e|
 	  fdefs << e.definition
	}
	rents = (sdefs + fdefs) - (sdefs & fdefs)
	if rents[0]
	  @text << INSTS+" "+MISMATCH+".\n"
	  rents.each{|e|
	    if sdefs.include?(e) ### extra is in SLICES
		  pt=@flat1.bounds.max
		  pt.z=@flat1.bounds.min.z
		  ve=@flat1.transformation.origin.vector_to(pt)
		  tr=Geom::Transformation.new()
		  inst=@flat1.entities.add_instance(e, tr)
		  tr=Geom::Transformation.translation(ve)
		  inst.transform!(tr)
		  @flat1.entities.parent.invalidate_bounds
		  @text << "  "+e.name+" "+ADDED+" "+TFLAT+" 'FLAT'.\n"
		elsif fdefs.include?(e) ### extra is in FLAT
		  inst=nil
		  @flat1.entities.grep(Sketchup::ComponentInstance).each{|slice|
		    if slice.definition==e
			  inst=slice
			  break
			end
		  }
		  inst.erase! if inst
		  @text << "  "+e.name+" "+REMOVED+" "+FROM+" 'FLAT'.\n"
		end
	  }
	else ### match
	  @text << INSMA+": "+OK+".\n"
	end
  end
  ###
  if @set2
    @slices2=@set2.entities.to_a
    @type2=nil
	@slices2.grep(Sketchup::ComponentInstance).each{|e|
	  @type2=e.definition.name
	  break
	}
	### repeat for set2
  ###
  @text << "\n"+s2name+"\n"
  ###
  ### remove non-instances from SLICES - e.g. clines
  sents=@set2.entities
  togos=[]
  sents.each{|e|
    next if e.is_a?(Sketchup::ComponentInstance)
	togos << e
  }
  @text << VALID+": "
  if togos[0]
    @text << NONER+" "+togos.length.to_s+".\n"
    sents.erase_entities(togos)
  else
	@text << OK+".\n"
  end
  ###
  @slices2=sents.to_a ### ==instances
  @type2=nil
  @slices2.grep(Sketchup::ComponentInstance).each{|e|
	@type2=e.definition.name
	break
  }
  ###
  ### check each slice1 for ref-tag
  names=[]
  @slices2.grep(Sketchup::ComponentInstance).each{|slice|
    names << slice.definition.name if slice.valid?
  }
  names.uniq!
  names.sort!
  ###
  defns=[]; names.each{|n|defns << @defns[n]}
  tags=[]; defns.each{|d|
    tag=nil ### ==NO tag
    d.entities.grep(Sketchup::Group).each{|e|
	  if e.name=="TEXT"
	    tag=e
	    break
	  end
    }
	tags << tag
  }
  ###
  txts=[]
  tags.each{|tag|
    if tag
	  txts << tag.get_attribute("Slicer5", "ref", "?")
	else
	  txts << "?"
	end
  }
  ###
 if tags.compact[0] ### There are some NOT nil tags
   ### fix broken tags, txt==nil it is 'unset'
   @text << TREFT+"...\n"
   names.each_with_index{|name, i|
    if tags[i]==nil ### missing tag
	  @text << name+" "+TMISS+".\n"
	  ents=@defns[name].entities
	  txt=name.split("-")[1..-1].join("-")
	  zz=@x0
	  ents.grep(Sketchup::Edge).each{|e|
	    if e.line[1].parallel?(Z_AXIS)
		  zz=e.length
		  break
		end
	  }
	  height=zz
	  tags.each{|tag|
	    if tag!=nil
		  height=tag.bounds.height
	      height=tag.bounds.width if tag.bounds.width<height
		  break
		end
	  }
	  pt=[zz,zz,zz]
	  rot=0 ### never XAR !
	  self.text_new(ents, pt, txt, height, rot)
	else ### has tag
	  ents=tags[i].entities
	  txt=name.split("-")[1..-1].join("-")
	  height=tags[i].bounds.height
	  height=tags[i].bounds.width if tags[i].bounds.width<height
	  if txts[i]=="?" ### BUT missing txt
	    @text << name+" "+RATMU+"..."+UPDATED+".\n"
		tags[i].set_attribute("Slicer5", "ref", txt)
		self.text_updater(ents, txt, height)
		tags[i].make_unique if ents.parent.instances[1]
      else ### has txt :)
	    if txts[i]==txt
		  @text << name+" "+TXTOK+".\n"
		else ### shows wrong txt :(
		  @text << name+" "+TXREF+" "+UPDATED+".\n"
		  tags[i].set_attribute("Slicer5", "ref", txt)
		  self.text_updater(ents, txt, height)
		  tags[i].make_unique if ents.parent.instances[1]
		end
	  end
	end
   }
 else ###  NO tags
   @text << TREFT+": "+NONE+".\n"
 end
  ### check for instance match SLICES v FLAT
  flatname=s2name.gsub(/^SLICES/,"FLAT")
  @defns.each{|d|
    next unless d.group?
	if d.instances[0].name==flatname
	  @flat2=d.instances[0]
	  break
	end
  }
  if @flat2==nil
    @text << FLAT+"2...\n"+NONE+".\n"
  else ### NO FLAT version
	###
    @text << "\n"+flatname+"\n"
	###
    sents=@flat2.entities
    togos=[]
    sents.each{|e|
      next if e.is_a?(Sketchup::ComponentInstance)
	  togos << e
    }
    @text << VALID+": "
    if togos[0]
      @text << NONER+" "+togos.length.to_s+".\n"
      sents.erase_entities(togos)
    else
	  @text << OK+".\n"
    end
	### check contents
	### compare instances definitions
	sdefs=[]
	@slices2.grep(Sketchup::ComponentInstance).each{|e|
	  sdefs << e.definition
	}
	fdefs=[]
	@flat2.entities.grep(Sketchup::ComponentInstance).each{|e|
	  fdefs << e.definition
	}
	rents = (sdefs + fdefs) - (sdefs & fdefs)
	if rents[0]
	  @text << INSTS+" "+MISMATCH+".\n"
	  rents.each{|e|
	    if sdefs.include?(e) ### extra is in SLICES
		  pt=@flat2.bounds.max
		  pt.z=@flat2.bounds.min.z
		  ve=@flat2.transformation.origin.vector_to(pt)
		  tr=Geom::Transformation.new()
		  inst=@flat2.entities.add_instance(e, tr)
		  tr=Geom::Transformation.translation(ve)
		  inst.transform!(tr)
		  @flat2.entities.parent.invalidate_bounds
		  @text << "  "+e.name+" "+ADDED+" "+TFLAT+" FLAT2.\n"
		elsif fdefs.include?(e) ### extra is in FLAT
		  inst=nil
		  @flat2.entities.grep(Sketchup::ComponentInstance).each{|slice|
		    if slice.definition==e
			  inst=slice
			  break
			end
		  }
		  inst.erase! if inst
		  @text << "  "+e.name+" "+REMOVED+" "+FROM+" 'FLAT2'.\n"
		end
	  }
	else ### match
	  @text << INSMA+": "+OK+".\n"
	end
  end
  ###
	###
  end#slices2
  ###
  @text << "\nEOF\n"
  ###
  ### write audit report
  report=File.join(File.dirname(@model.path), @model.title+"_SLICES-"+@rnd+"_"+AREP+".txt").tr("\\","/")
  begin
    file=File.open(report,"w")
	 file.puts(@text)
	file.close
  rescue
    UI.messagebox(@msg+"\n\n"+ARNOT+"\n"+report+"\n"+ARPRO+"..."+"\n"+NOCHA+".")
	@model.abort_operation
	return nil
  end
  ###
  @model.active_view.refresh
  if File.exist?(report)
    if rep
      UI.beep
      UI.openURL("file:///"+report)
    else
	  if @text=~/#{ADDED}/ || @text=~/#{MISMATCH}/
	    UI.messagebox(@msg+"\n\n"+SHOWS+"...")
		UI.openURL("file:///"+report)
	    return false
	  end
	end
  end
  ###
 @model.commit_operation
 return true
  ###
end#audit
def text_updater(ents=[], txt="?", height=12.mm)
  ents.erase_entities(ents.to_a)
  tup=ents.add_3d_text(txt,TextAlignLeft,'Txt',false,false,height,0,0,false,0)
end#text_updater
def text_new(ents=[], pt=[0,0,0], txt="?", height=12.mm, rot=0)
  tgp=ents.add_group()
  tgp.entities.add_3d_text(txt,TextAlignLeft,'Txt',false,false,height,0,0,false,0)
  if rot!=0 ### XAR
    pt.x= -pt.x
  end
  tr=Geom::Transformation.new(pt)
  tgp.transform!(tr)
  if rot!=0 ### XAR
    tr=Geom::Transformation.rotation(pt, Z_AXIS, rot)
	tgp.transform!(tr)
  end
  tgp.set_attribute("Slicer5", "ref", txt)
  tgp.layer=@textLayer
  tgp.name="TEXT"
end#text_new

################
end#Slicer class
################

##############
end#TIG module
##############
