=begin
Copyright 2008-2009, Asteronimo (asteronimo@gmail.com)

Permission to use, copy, modify, and distribute this software for
any purpose and without fee is hereby granted, provided the above
copyright notice appears 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.

#-----------------------------------------------------------------------
  Installation:
      Copy the script SliceModeler.rb to the Google SketchUp's Plugin folder.
      Restart SketchUp. Create a cube of dimensions 2000mm x 2000mm x 2000mm, 
      select the object and choode SliceModeler from Plugin menu.
#-----------------------------------------------------------------------
  IF YOU FIND THIS SCRIPT AT ALL USEFUL I'D WELCOME YOUR DONATION AT:
  http://www.public-art-international.com/catalog/product_info.php/products_id/200

  On that webpage you can also the new version 1.4
#-----------------------------------------------------------------------

Name        : SliceModeler.rb

Description : A tool to 'Slice' up a volume along 2 axes - useful for 
              rapid prototyping of real 3D models out of sheet material 
              like 3mm MDF or plexiglas.
              
              The 'thickness' parameter is used to determine the thickness
              of the slices and hence the width of the slot where 2 slices 
              interlock. 
              
Author      : Asteronimo (asteronimo@gmail.com)

Menu Item   : Plugins -> SliceModeler

Date        : 01/2009

Type        : Tool

Version     :
1.0 23/12/08  First working version based on TIG's Slicer script but without 
              transforming the slots to flattened slices.
1.1 27/12/08  Solved bug in slot position calculation
1.2 27/12/08  Using additional slices (3 for each intersection) to calculate slot 
              holes.
			  Improved parameter dialog and transforming slots to flattened slices.
1.3 03/01/09  Discovered that text labels add size to to bounding box dimensions.
              Corrected text labeling.
  			  Corrected contour cutting.
              Slices now keep their attributed thickness.
              Improved code for slicing in Z-direction and spreading the
              flattened slices. 
#-----------------------------------------------------------------------
=end


require 'sketchup.rb'

class SliceModeler

    ##################################################################################################
    #
    # --- Function that starts it all off
    #
    ##################################################################################################
    def SliceModeler::calculate

        @logging = false  # logging of intersections on/off
        @tl_size = 0.0.mm # text label size
                
        ### version error
        if (Sketchup.version.split(".")[0].to_i<5)
             UI.beep
             UI.messagebox("Sorry.  This is Version 5+ program only !")
             return nil
        end #if

        model = Sketchup.active_model
        model.active_layer = nil
        layer = nil
        entities = model.entities
        definitions = model.definitions
        model.start_operation "SliceModeler"
        ss = model.selection
        view = model.active_view
        sfix = Time::now.to_i.to_s[3..-1]

        if ss.empty?
           UI.beep
           UI.messagebox("NO Selection !")
           return nil
        end #if

        if ss[1]
           UI.beep
           UI.messagebox("Selection MUST be ONE Group or Component !")
           return nil
        end #if

        if ss[0].typename != "Group" and ss[0].typename != "ComponentInstance"
           UI.beep
           UI.messagebox("Selection is NOT a Group or Component !")
        end #if

        selected = ss[0]

        #-------------------------------------------------------------------------------------------
        ### slice arrays
        pslice      = []
        arr         = []
        ### save xyz setting for each slice
        xyz_setting = []
        #-------------------------------------------------------------------------------------------
        ### do slicing twice, for 2 directions: X & Y, X & Z, Y & Z, etc.
        @nn = 1
        while @nn < 3
            # ----------- set up @spacing, @thickness and @xyz in def dialog for later
            # ----------- make cutting disc face at xy bounds
            ###
            # ----------- dialog ----------------
            ### show VCB and status info
            Sketchup::set_status_text(("SLICEMODELER PARAMETERS..." ), SB_PROMPT)
            Sketchup::set_status_text(" ", SB_VCB_LABEL)
            Sketchup::set_status_text(" ", SB_VCB_VALUE)
            return nil if not SliceModeler.dialog

            xyz_setting[@nn] = @xyz

            ### parent group
            pslice[@nn]      = entities.add_group
            psliceentities   = pslice[@nn].entities
            pslice[@nn].name = "Slice-" + @xyz + "-" + sfix

            @sbb = selected.bounds
            xmin = @sbb.min.x
            xmax = @sbb.max.x
            ymin = @sbb.min.y
            ymax = @sbb.max.y
            zmin = @sbb.min.z
            zmax = @sbb.max.z

            #### ------- slice, thickness & xyz inc ------------------------------
            ### orientation ?
            if @xyz == "X"
	            pnt1 = [xmin,ymin,zmin] 
	            pnt2 = [xmin,ymax,zmax]
	            dist = ([xmin,0,0].distance [xmax,0,0])
            elsif @xyz == "Y"
	            pnt1 = [xmin,ymin,zmin] 
	            pnt2 = [xmax,ymin,zmax]
	            dist = ([0,ymin,0].distance [0,ymax,0])
            elsif @xyz == "Z"
	            pnt1 = [xmin,ymin,zmin] 
	            pnt2 = [xmax,ymax,zmin]
	            dist = ([0,0,zmin].distance [0,0,zmax])
            end #if
            
            spacing = @spacing

            if @nn == 1
               ### ask if number slices
               Sketchup::set_status_text(("ADD A NUMBER TO EACH SLICE ?" ), SB_PROMPT)
               Sketchup::set_status_text(" ", SB_VCB_LABEL)
               Sketchup::set_status_text(" ", SB_VCB_VALUE)

               @num = 1
               @num = 0 if UI.messagebox("Add a Number to each Slice ?   ",MB_YESNO,"Number ?")==7 ##=NO
            end #if

            ### even out the slices for total width
            q = (dist/(@spacing + @thickness)).floor
            myqdist = q * (@spacing + @thickness)

            ### check for minimum left spacing
            if dist - myqdist < 4 * @thickness
               q -= 1
               myqdist = q * (@spacing + @thickness)
            end #if
            offset = (dist - myqdist + @spacing + @thickness)/2

            UI.messagebox("distance:" + dist.to_s + "\nspacing: " + @spacing.to_s + "\nthickness: " + @thickness.to_s + "\nnumber: " + q.to_s + "\noffset: " + offset.to_s)

            q += 1

            if q > 25 ### max number of slices that don't take too long...
               ### show VCB and status info
               Sketchup::set_status_text(("LARGE NUMBER OF SLICES ? " ), SB_PROMPT)
               Sketchup::set_status_text(" ", SB_VCB_LABEL)
               Sketchup::set_status_text(" ", SB_VCB_VALUE)
               (return nil if UI.messagebox("This will make #{q.to_s} slices !   \nDo you want to continue ?   ",MB_YESNO,"Slice Count !")==7)##=NO
            end #if

            @ctr = Geom.linear_combination(0.5,pnt1,0.5,pnt2)
            @rad = (pnt1.distance pnt2) ### make a LOT bigger than bb
            ###
            disc = entities.add_group
            discentities = disc.entities
            if @xyz == "Z"
               disccircle = SliceModeler.circle(@ctr,[0,0,-1],@rad,24)
            elsif @xyz == "Y" 
               disccircle = SliceModeler.circle(@ctr,[0,-1,0],@rad,24)
            elsif @xyz == "X"
               disccircle = SliceModeler.circle(@ctr,[-1,0,0],@rad,24) 
            end #if
            #
            discentities.add_face disccircle

            # ------------------------ do each of slice
            ###
            ### intersect disc and selected
            ###
            ### The intersect_with method is used to intersect entities, a component instance, or group
            ### with an entities object.
            ###
            ### entities.intersect_with recurse, transformation1, entities1, transformation2, hidden, entities2
            ###
            ### recurse - true if you want this entities object to be recursed
            ###   (intersection lines will be put inside of groups and components within this entities object)
            @recurse_flag = true
            ### transformation1 - the transformation for this entities object
            ### entities1 - the entities (group etc) where you want the intersection lines to appear
            ### transformation2 - the transformation for entities1
            ### hidden - true if you want hidden geometry in this entities object to be used in the intersection
            ###   (it's false in section cuts)
            @hidden_flag = true
            ### entities2 - an entities object (or an array of entities ?)

            ### loop through all possible slices - bottom up
            t = @thickness * 1.5 ### used to position text label
            m = spacing * 1      ### used to position text label
            s = offset           ### for the SliceModeler we want to even out the slices,
                                 ###   instead of using fixed offset 0.05.mm
            slices = []

            #-------------------------------------------------------------------------------------------
            c = 1
            while c < q
                slice = psliceentities.add_group ### add into parent group
                sliceentities = slice.entities
                slice.name = "Slice-" + c.to_s

                ### show VCB and status info
                Sketchup::set_status_text(("MAKING SLICE EDGES #{c} of #{q}"), SB_PROMPT)
                Sketchup::set_status_text(" ", SB_VCB_LABEL)
                Sketchup::set_status_text(" ", SB_VCB_VALUE)

                ### do cut
                if @xyz == "X"
                   disc.move! Geom::Transformation.new [s,0,0] 
                elsif @xyz == "Y"
                   disc.move! Geom::Transformation.new [0,s,0] 
                elsif @xyz == "Z"
                   disc.move! Geom::Transformation.new [0,0,s] 
				end #if
				
                discentities.intersect_with @recurse_flag, disc.transformation, slice, slice.transformation, @hidden_flag, selected
                slice.layer = layer if slice
                ###
                slices = slices.push(slice) ### keep list of slices for later use
                ###
                s += spacing + @thickness
                
                c += 1
            end #while

            #-------------------------------------------------------------------------------------------

            ### delete disc
            disc.erase!

            ### loop through all slices
            j = slices.length
            k = 1
            area = 0

            ###
            for slice in slices
                ### show VCB and status info
                Sketchup::set_status_text(("PROCESSING SLICES #{k} of #{j}" ), SB_PROMPT)
                Sketchup::set_status_text(" ", SB_VCB_LABEL)
                Sketchup::set_status_text(" ", SB_VCB_VALUE)

                sliceentities = slice.entities

                ### face the slices
                for e in sliceentities
                   e.find_faces if e.typename == "Edge"
                end #for e

                faces = []
                for f in sliceentities
                   faces = faces.push(f) if f.typename == "Face"
                end #for f

                faces = faces.uniq

                for face in faces
                   if  @xyz == "X"   
                      face.reverse! if face.normal == [-1,0,0] 
                   elsif  @xyz == "Y"
                      face.reverse! if face.normal == [0,-1,0] 
                   elsif  @xyz == "Z"
                      face.reverse! if face.normal == [0,0,-1] 
                   end #if   
                end #for face

                ### now work out which is which...
                (faces.length-1).times do |this|
                    for face in faces
                       if face.valid?
                          for edgeuse in face.outer_loop.edgeuses
                             if not edgeuse.partners[0] ### outermost face
                                faces = faces - [face]
                                loops = face.loops
                                for loop in loops
                                   for fac in faces
                                      if fac.valid? and (fac.outer_loop.edges - loop.edges) == []
                                         faces = faces - [fac]
                                         fac.erase! ### fac abutts kept face so it must be erased...
                                      end #if fac
                                   end #for fac
                                end #for loop
                             end #if outermost
                          end #for edgeuse
                       end #if valid
                    end #for face
                end #times

                ### -------------- now give slices thickness
                faces = []
                for f in sliceentities
                    faces = faces.push(f) if f.typename == "Face"
                end #for f

                for f in faces
                   if @material == "<Default>"
                      f.material = nil
                   else
                      f.material = @material
                   end #if

                   f.layer = layer
                   for e in f.edges
                      e.layer = layer
                   end #for e
                   area += f.area
                end #for f

                for f in faces
                   f.reverse! if @thickness == 0 and @xyz == "Y"
                end #for f

                ### clear VCB and status info
                Sketchup::set_status_text((" " ), SB_PROMPT)
                Sketchup::set_status_text(" ", SB_VCB_LABEL)
                Sketchup::set_status_text(" ", SB_VCB_VALUE)

                k += 1
            end #for slice in slices

            if area == 0
               UI.beep
               UI.messagebox("There is NO Volume to Slice !")
               pslice[@nn].erase! if pslice[@nn].valid?
               return nil
            end #if

            #----------- sort Layer settings etc
            layerSlice = model.layers.add(@layerSlice)
            layerSlice.visible = true
            pslice[@nn].layer = layerSlice

            ### unselected...
            model.selection.clear

            ### hide selected
            xray = 0
            if not model.rendering_options["ModelTransparency"] ### NOT xray mode
               selected.hidden = true
               if UI.messagebox("Do you want to leave the Original Selection 'Hidden' for now ?  ",MB_YESNO,"Volume Hidden ?") == 7 ### 6=YES 7=NO
                  xray = 1
                  selected.hidden = false
               end #if
            end #if

            ### xray ?
            xrayq = 0
            if not model.rendering_options["ModelTransparency"]
               xrayq = 1
               model.rendering_options["ModelTransparency"]=true if xray == 1
            end #if

            ### ---- undo xray IF was not on already
            if xray == 1 and xrayq == 1
               model.rendering_options["ModelTransparency"]=false if UI.messagebox("Do you want to leave 'Xray Mode' on for now ?  ",MB_YESNO,"Volume Xray ?") == 7 ###7=NO
            end #if

            ### save slices in array for later use
#            arr[@nn] = slices.dup

            @nn += 1
        end #while
        #-------------------------------------------------------------------------------------------

        ### now calculate the intersecion for each crossing of slices at perpendicular angle

        ### default thickness is 3mm
        ### I use mainly 3mm MDF and 3mm Plexiglas to make 3d slice models
        
        ### arrays to save holes
        holefaces = []

        Sketchup::set_status_text(("FINDING EDGES OF INTERSECTIONS... " ), SB_PROMPT)
        Sketchup::set_status_text(" ", SB_VCB_LABEL)
        Sketchup::set_status_text(" ", SB_VCB_VALUE)
        
        ###---------------------------------------------------------
        ### need to do this for both directions
        rr = 1
        while rr < 3
            ### way to change the array that intersects the other array
            if rr == 1
               aaa = 1
               bbb = 2
            else
               aaa = 2
               bbb = 1
            end #if

            for l in pslice[aaa].entities
               for k in pslice[bbb].entities
	              ###
                  kb    = k.bounds
	              kxmin = kb.min.x
	              kxmax = kb.max.x
	              kymin = kb.min.y
	              kymax = kb.max.y
	              kzmin = kb.min.z
	              kzmax = kb.max.z
	              #
	              kp  = []
		          p_l = []
		          p_c = []
		          p_r = []
	              #
	              if xyz_setting[bbb] == "Z"
		            kpnt1 = [kxmin,kymin,kzmin] 
		            kpnt2 = [kxmax,kymax,kzmin]
		            kdist = ([0,0,kzmin].distance [0,0,kzmax])
                    #
 	                kp = kp.push([kxmin,@sbb.min.y,kzmin])    
	                kp = kp.push([kxmin,@sbb.max.y,kzmin])    
	                kp = kp.push([kxmax,@sbb.max.y,kzmin])    
	                kp = kp.push([kxmax,@sbb.min.y,kzmin])    
	              elsif xyz_setting[bbb] == "Y"
		            kpnt1 = [kxmin,kymin,kzmin] 
		            kpnt2 = [kxmax,kymin,kzmax]
		            kdist = ([0,kymin,0].distance [0,kymax,0])
		            #
 	                kp = kp.push([@sbb.min.x,kymin,kzmin])    
	                kp = kp.push([@sbb.max.x,kymin,kzmin])    
	                kp = kp.push([@sbb.max.x,kymin,kzmax])    
	                kp = kp.push([@sbb.min.x,kymin,kzmax])    
	              elsif xyz_setting[bbb] == "X"
		            kpnt1 = [kxmin,kymin,kzmin] 
		            kpnt2 = [kxmin,kymax,kzmax]
		            kdist = ([kxmin,0,0].distance [kxmax,0,0])
		            #
 	                kp = kp.push([kxmin,@sbb.min.y,kzmin])    
	                kp = kp.push([kxmin,@sbb.max.y,kzmin])    
	                kp = kp.push([kxmin,@sbb.max.y,kzmax])    
	                kp = kp.push([kxmin,@sbb.min.y,kzmax])    
	              end #if
	              
	              dsc = entities.add_group 
	              dscentities = dsc.entities
	              dscentities.add_face kp
                  
                  ### Now find 3 intersections between the 2 slices:
                  ###   1) 'left' side of the slot, needed for x1-position
                  ###   2) middle line of the slot, needed to calculate (half) slot depth
                  ###   3) 'right' side of the slot, needed for x2-position
                  slice_repository = []
                  slicepos = -@thickness/2
                  #
                  tmpcnt = 0
                  #
                  1.upto(3) { |num|
                      tmp_slices = []
                      #
	                  tmpgroup = entities.add_group ### add into model group
	                  ### move to 'left' side of slot
	                  if xyz_setting[bbb] == "Z"
	                     dsc.move! Geom::Transformation.new [0,0,slicepos]
	                  elsif xyz_setting[bbb] == "Y"
	                     dsc.move! Geom::Transformation.new [0,slicepos,0]
	                  elsif xyz_setting[bbb] == "X"
	                     dsc.move! Geom::Transformation.new [slicepos,0,0]
	                  end #if
	                  #
	                  dscentities.intersect_with true, dsc.transformation, tmpgroup, tmpgroup.transformation, true, l
	                  #
                      tmpgroup.entities.each { |e|
                         #puts "Slice: " + e.start.position.to_s + ", " + e.end.position.to_s
                         tmp_slices = tmp_slices.push([e.start.position,e.end.position])
                         tmpcnt += 1
                      } #each
	                  
	                  slice_repository = slice_repository.push(tmp_slices)
	                  #
	                  tmpgroup.erase!
	                  #
	                  slicepos += @thickness/2
                  }
                  
                  ### Solve issue when there are more 2 or more intersections
                  if tmpcnt > 0 and (tmpcnt % 3) == 0 # if all 3 intersections were OK
	                  if xyz_setting[bbb] == "Z"
	                     p_l = SliceModeler.xsort(slice_repository.shift)
	                     p_c = SliceModeler.xsort(slice_repository.shift)
	                     p_r = SliceModeler.xsort(slice_repository.shift)
	                  elsif xyz_setting[bbb] == "X" or xyz_setting[bbb] == "Y"
	                     p_l = SliceModeler.zsort(slice_repository.shift)
	                     p_c = SliceModeler.zsort(slice_repository.shift)
	                     p_r = SliceModeler.zsort(slice_repository.shift)
	                  end #if
                      #
                      c = tmpcnt/3
                      #
                      pts = []
                      #
                      if xyz_setting[bbb] == "X"
                          if xyz_setting[aaa] == "Y"
                              0.upto(c-1) { |ccnt|
                   	             zm = (p_c[ccnt][1].z + p_c[ccnt][0].z)/2
                                 pts[0] = Geom::Point3d.new([p_l[ccnt][0].x,p_l[ccnt][0].y,p_l[ccnt][0].z])
                                 pts[1] = Geom::Point3d.new([p_l[ccnt][0].x,p_l[ccnt][0].y,zm])
                                 pts[2] = Geom::Point3d.new([p_r[ccnt][0].x,p_r[ccnt][0].y,zm])
                                 pts[3] = Geom::Point3d.new([p_r[ccnt][0].x,p_r[ccnt][0].y,p_r[ccnt][0].z])
                                 #
                                 SliceModeler.log_intersection(ccnt,pts) if @logging
	                             ### save calculate slot-edges for later use
		                         holefaces = holefaces.push([rr,l,pts.dup])
                              }
                          elsif xyz_setting[aaa] == "Z"
                              0.upto(c-1) { |ccnt|
                   	             zm = (p_c[ccnt][1].y + p_c[ccnt][0].y)/2
                                 pts[0] = Geom::Point3d.new([p_l[ccnt][1].x,p_l[ccnt][1].y,p_l[ccnt][1].z])
                                 pts[1] = Geom::Point3d.new([p_l[ccnt][1].x,zm,            p_l[ccnt][1].z])
                                 pts[2] = Geom::Point3d.new([p_r[ccnt][1].x,zm,            p_r[ccnt][1].z])
                                 pts[3] = Geom::Point3d.new([p_r[ccnt][1].x,p_r[ccnt][1].y,p_r[ccnt][1].z])
                                 #
                                 SliceModeler.log_intersection(ccnt,pts) if @logging
		                         ### save calculate slot-edges for later use
		                         holefaces = holefaces.push([rr,l,pts.dup])
                              }
                          end #if
                      elsif xyz_setting[bbb] == "Y"
                          if xyz_setting[aaa] == "X"
                              0.upto(c-1) { |ccnt|
                   	             zm = (p_c[ccnt][1].z + p_c[ccnt][0].z)/2
                                 pts[0] = Geom::Point3d.new([p_l[ccnt][1].x,p_l[ccnt][1].y,p_l[ccnt][1].z])
                                 pts[1] = Geom::Point3d.new([p_l[ccnt][1].x,p_l[ccnt][1].y,zm])
                                 pts[2] = Geom::Point3d.new([p_r[ccnt][1].x,p_r[ccnt][1].y,zm])
                                 pts[3] = Geom::Point3d.new([p_r[ccnt][1].x,p_r[ccnt][1].y,p_r[ccnt][1].z])
                                 #
                                 SliceModeler.log_intersection(ccnt,pts) if @logging
	                             ### save calculate slot-edges for later use
		                         holefaces = holefaces.push([rr,l,pts.dup])
	                          }
	                      elsif xyz_setting[aaa] == "Z"
	                          0.upto(c-1) { |ccnt|
                   	             zm = (p_c[ccnt][1].x + p_c[ccnt][0].x)/2
	                             pts[0] = Geom::Point3d.new([p_l[ccnt][1].x,p_l[ccnt][1].y,p_l[ccnt][1].z])
	                             pts[1] = Geom::Point3d.new([zm,            p_l[ccnt][1].y,p_l[ccnt][1].z])
	                             pts[2] = Geom::Point3d.new([zm,            p_r[ccnt][1].y,p_r[ccnt][1].z])
	                             pts[3] = Geom::Point3d.new([p_r[ccnt][1].x,p_r[ccnt][1].y,p_r[ccnt][1].z])
                                 #
                                 SliceModeler.log_intersection(ccnt,pts) if @logging
	                             ### save calculate slot-edges for later use
		                         holefaces = holefaces.push([rr,l,pts.dup])
                              }
                          end #if
                      elsif xyz_setting[bbb] == "Z"
                          if xyz_setting[aaa] == "X"
                              0.upto(c-1) { |ccnt|
                   	             zm = (p_c[ccnt][1].y + p_c[ccnt][0].y)/2
                                 pts[0] = Geom::Point3d.new([p_l[ccnt][0].x,p_l[ccnt][0].y,p_l[ccnt][0].z])
                                 pts[1] = Geom::Point3d.new([p_l[ccnt][0].x,zm,            p_l[ccnt][0].z])
                                 pts[2] = Geom::Point3d.new([p_r[ccnt][0].x,zm,            p_r[ccnt][0].z])
                                 pts[3] = Geom::Point3d.new([p_r[ccnt][0].x,p_r[ccnt][0].y,p_r[ccnt][0].z])
                                 #
                                 SliceModeler.log_intersection(ccnt,pts) if @logging
	                             ### save calculate slot-edges for later use
		                         holefaces = holefaces.push([rr,l,pts.dup])
                              }
                          elsif xyz_setting[aaa] == "Y"
                              0.upto(c-1) { |ccnt|
                   	             zm = (p_c[ccnt][1].x + p_c[ccnt][0].x)/2
	                             pts[0] = Geom::Point3d.new([p_l[ccnt][0].x,p_l[ccnt][0].y,p_l[ccnt][0].z])
	                             pts[1] = Geom::Point3d.new([zm,            p_l[ccnt][0].y,p_l[ccnt][0].z])
	                             pts[2] = Geom::Point3d.new([zm,            p_r[ccnt][0].y,p_r[ccnt][0].z])
	                             pts[3] = Geom::Point3d.new([p_r[ccnt][0].x,p_r[ccnt][0].y,p_r[ccnt][0].z])
                                 #
                                 SliceModeler.log_intersection(ccnt,pts) if @logging
		                         ### save calculate slot-edges for later use
		                         holefaces = holefaces.push([rr,l,pts.dup])
                              }
                          end #if
                      end # if
                  end #if
                  
                  dsc.erase!
               end #for
               
            end #for
            
            rr += 1
        end #while rr
               
        #-------------------------------------------------------------------------------------------

        Sketchup::set_status_text(("MAKING SLOT HOLES... " ), SB_PROMPT)
        Sketchup::set_status_text(" ", SB_VCB_LABEL)
        Sketchup::set_status_text(" ", SB_VCB_VALUE)

        # add faces to entities to create holes
        1.upto(2) { |k|
           c = 1
	       pslice[k].entities.each { |l|
	          holefaces.each { |n,e,p|
	             if n == k and l == e 
	                # create face from 3 edges by splitting plane
			        edges = l.entities.add_edges p
			        ed = edges[0]
			        del_edges = []
			        for f in ed.faces
			           # find superfluous edges
			           if f.edges.length < 7
			       	      f.edges.each { |fe|
			                 del_edges = del_edges.push(fe) if fe.faces.length == 1
			              } #each
			              # delete face to create hole
				          f.erase! if f.edges.length < 7
				          # delete superfluous edges
		                  del_edges.each { |d|
		                     l.entities.erase_entities d 
		                  }
		               end #if   
				    end #for
		         end #if
	          }
	          # delete leftover edges without face
              l.entities.each { |e|
                 if e.typename == "Edge" 
                    e.erase! if e.faces == 0
                 end #if 
              } #each
              ###
              # text label ?
              if @num == 1
	              bb = l.bounds
	              zmin = bb.min.z
	              ymin = bb.min.y
	              xmin = bb.min.x
	              ###
	              if xyz_setting[k] == "X"
	                 tt = l.entities.add_text(xyz_setting[k].to_s + c.to_s, [xmin+t,ymin+m,zmin+m]) 
	              elsif xyz_setting[k] == "Y"
	                 tt = l.entities.add_text(xyz_setting[k].to_s + c.to_s, [xmin+m,ymin+t,zmin+m]) 
	              elsif xyz_setting[k] == "Z"
	                 tt = l.entities.add_text(xyz_setting[k].to_s + c.to_s, [xmin+m,ymin+m,zmin+t]) 
	              end #if
	              ###
	              tt.layer = layer if tt
	              #
	              @tl_size = (tt.bounds.max.y - tt.bounds.min.y)/2

              end #if
              c += 1
	       } #each
        } #upto
        
        #### 
        # give slices thickness
        # to do that we have to move the slices a distance of @thickness/2
        if @thickness != 0
	        if xyz_setting[1] == "Z"
	           if xyz_setting[2] == "X"
	              #puts "Z-X"
		          pslice[1].move! Geom::Transformation.new [@thickness/2,0,0]
		          pslice[2].move! Geom::Transformation.new [0,0,@thickness/2]
	           elsif xyz_setting[2] == "Y"
	              #puts "Z-Y"
		          pslice[1].move! Geom::Transformation.new [0,@thickness/2,0]
		          pslice[2].move! Geom::Transformation.new [0,0,@thickness/2]
	           end #if
	        elsif xyz_setting[1] == "X"
	           if xyz_setting[2] == "Y"
	              #puts "X-Y"
		          pslice[1].move! Geom::Transformation.new [0,@thickness/2,0]
		          pslice[2].move! Geom::Transformation.new [@thickness/2,0,0]
	           elsif xyz_setting[2] == "Z"
	              #puts "X-Z"
		          pslice[1].move! Geom::Transformation.new [0,0,@thickness/2]
		          pslice[2].move! Geom::Transformation.new [@thickness/2,0,0]
		       end #if
	        elsif xyz_setting[1] == "Y"
	           if xyz_setting[2] == "X"
	              #puts "Y-X"
		          pslice[1].move! Geom::Transformation.new [@thickness/2,0,0]
		          pslice[2].move! Geom::Transformation.new [0,@thickness/2,0]
	           elsif xyz_setting[2] == "Z"
	              #puts "Y-Z"
		          pslice[1].move! Geom::Transformation.new [0,0,@thickness/2]
		          pslice[2].move! Geom::Transformation.new [0,@thickness/2,0]
		       end #if   
	        end #if
		    #
	        1.upto(2) { |k|
		       pslice[k].entities.each { |l|
	              faces = []
	              for f in l.entities
	                  faces = faces.push(f) if f.typename == "Face"
	              end #for f
	              for f in faces
	                 (f.pushpull @thickness)
	                 f.reverse! if @xyz == "Y"
	              end #for f
		       }
	        } 
        end #if
        
        ### Flatten ?
        if UI.messagebox("Do you want to make 'Flattened' copies of the Slices ?  ",MB_YESNO,"Flatten ?") == 6 ### 6=YES 7=NO

           if SliceModeler.gap ### get gap

             ### VCB and status info
             Sketchup::set_status_text(("FLATTENING SLICES..." ), SB_PROMPT)
             Sketchup::set_status_text(" ", SB_VCB_LABEL)
             Sketchup::set_status_text(" ", SB_VCB_VALUE)

             gap = @gap

             #-------------------------------------------------------------------------------------------
             ### calculate flattened slices for both directions
             y_offset = 0
             
             @nn = 1
             while @nn < 3
                fslice = pslice[@nn].copy
                fslice.name = "Flat-" + xyz_setting[@nn].to_s + "-" + sfix
                fents = fslice.entities
                fs = fents.length
                fb = pslice[@nn].bounds
                z = fb.min.z
                y = fb.min.y
                x = fb.min.x
                if xyz_setting[@nn] == "Z"
                   y  = fb.max.y - fb.min.y
                else
                   ym = fb.max.z - fb.min.z
                end #if z

                if xyz_setting[@nn] == "X"
                   xm = fb.max.z - fb.min.z
                   yg = fb.max.y - fb.min.y
                   xs = x
                end #if x
                xx = 0
                yy = 0
###                zz = 0 # not used
                sq = Math::sqrt(fs).to_i ###round
                sqq = sq
                c = 0

                for e in fents
                   e.layer = layer
                   p = e.bounds.min
                   px = p.x
                   py = p.y
                   pz = p.z
                   pp = e.bounds.max
                   pxx = pp.x
                   pyy = pp.y
                   pzz = pp.z
                   #
#                   pt = [xx,yy,z - pz]     if xyz_setting[@nn] == "Z"
                   if xyz_setting[@nn] == "X"
                      pt = [xx,y - py,z - pz]     
                   elsif xyz_setting[@nn] == "Y"
                      pt = [xx,y - py,z - pz]     
                   elsif xyz_setting[@nn] == "Z"
                      pt = [xx,yy,-pz - @tl_size] 
                   end #if
                   #
                   tr = Geom::Transformation.new pt
                   e.transform! tr

                   # give slices thickness
                   #e.pushpull(@thickness) if @thickness != 0 and e.typename == "Face"

                   if xyz_setting[@nn] == "X"
                      xx = xx + xm + gap 
                   else
                      xx = xx + (pxx - px) + gap 
 				   end #if
 				   	
                   if c == sqq and xyz_setting[@nn] == "Z"
                      yy = yy + (y + gap)
                      xx = 0
                      sqq = sqq + sq + 1
                   elsif c == sqq and xyz_setting[@nn] != "Z"
                      if xyz_setting[@nn] == "Y"
                         y = y + (ym + gap) 
                      elsif xyz_setting[@nn] == "X"
                         y = y + (yg + gap) 
                      end #if
                      if xyz_setting[@nn] == "Y"
                         xx = 0 
                      elsif xyz_setting[@nn] == "X"
                         xx = xs - px 
                      end #if
                      sqq = sqq + sq + 1
                   end #if

                   c += 1
                end #for e

                ### this forces bb to correct itself...
                tmp = fents.add_line fents[0].bounds.min,fents[fents.length - 1].bounds.max
                tmp.erase!

                ### rotation for XY versions
                if xyz_setting[@nn] != "Z"
                   r = Math::PI / 2
                   for e in fents
                      eb = e.bounds.min
                      p = [eb.x,eb.y,fb.min.z]
                      if xyz_setting[@nn] == "X"
                         v = Geom::Vector3d.new 0,-1,0 
                      elsif xyz_setting[@nn] == "Y"
                         v = Geom::Vector3d.new -1,0,0 
                      elsif xyz_setting[@nn] == "Z" ###
                         v = Geom::Vector3d.new 0,0,-1 
                      end #if
                      rotate = Geom::Transformation.rotation p,v,r
                      e.transform! rotate
                   end #for e
               end #if !=Z
                                  
                ### this forces bb to correct itself...
                tmp = fents.add_line fents[0].bounds.min,fents[fents.length - 1].bounds.max
                tmp.erase!

                if @nn == 1
                   y_offset = fslice.bounds.max.y
                   #puts y_offset.to_s
                else  
                   fslice.move! Geom::Transformation.new [0,@gap+(fslice.bounds.max.y-fslice.bounds.min.y)/2+y_offset,0]
                end #if
                
                #----------- sort Layer settings etc for 'flat' layer...
                layerSlice = ("FLAT-" + @layerSlice) ### v3.0
                layerSlice = model.layers.add(layerSlice)
                # layerSlice.page_behavior = (LAYER_IS_HIDDEN_ON_NEW_PAGES | LAYER_HIDDEN_BY_DEFAULT)
                layerSlice.visible = true
                fslice.layer = layerSlice

                @nn += 1
             end #while
             #-------------------------------------------------------------------------------------------

             ### VCB and status info
             Sketchup::set_status_text(("FLATTENING SLICES DONE." ), SB_PROMPT)
             Sketchup::set_status_text(" ", SB_VCB_LABEL)
             Sketchup::set_status_text(" ", SB_VCB_VALUE)

          end #if Gap
        end #if Flatten

        #-------------------------------------------------------------------------------------------

        ### update view
        view.invalidate

        ### VCB and status info
        Sketchup::set_status_text(("DONE!" ), SB_PROMPT)
        Sketchup::set_status_text(" ", SB_VCB_LABEL)
        Sketchup::set_status_text(" ", SB_VCB_VALUE)

        # ---------------------- Close/commit group
        model.commit_operation

    end #def

    ##################################################################################################
    #
    # --- SliceModeler::log_intersection(ccnt,pts)
    #
    ##################################################################################################
    def SliceModeler::log_intersection(ccnt,pts)
        puts "---- Intersection " + ccnt.to_s + "--------------------------"
        puts "0: " + pts[0].to_s + ", 1: " + pts[1].to_s
        puts "2: " + pts[2].to_s + ", 3: " + pts[3].to_s
        puts "------------------------------"
    end #def
    
    ##################################################################################################
    #
    # --- SliceModeler::xsort(ar)
    #
    ##################################################################################################
    def SliceModeler::xsort(ar)
        sortar = []
        #
        ar.each { |n|
           if n[0].x > n[1].x
              tmp = n[0]
              n[0] = n[1]
              n[1] = tmp
           end #if
        }
        #
        while ar.size > 0
           ns = 0
           1.upto(ar.size-1) { |n|
              if ar[n][1].x > ar[ns][1].x
                ns = n
              end #if
           }
           sortar = sortar.push(ar[ns])
           ar.delete_at(ns)
        end #while
        #
        return sortar
    end #def
    
    ##################################################################################################
    #
    # --- SliceModeler::zsort(ar)
    #
    ##################################################################################################
    def SliceModeler::zsort(ar)
        sortar = []
        #
        ar.each { |n|
           if n[0].z > n[1].z
              tmp = n[0]
              n[0] = n[1]
              n[1] = tmp
           end #if
        }
        #
        while ar.size > 0
           ns = 0
           1.upto(ar.size-1) { |n|
              if ar[n][1].z > ar[ns][1].z
                ns = n
              end #if
           }
           sortar = sortar.push(ar[ns])
           ar.delete_at(ns)
        end #while
        #
        return sortar
    end #def
    
    ##################################################################################################
    #
    # --- Dialog to enter spacing, thickness, layer and colour
    #
    ##################################################################################################
    def SliceModeler::dialog

        ### get current units ----------------------
        currentunits1 = Sketchup.format_length 120000
        currentunits2 = Sketchup.format_length 1
        units = "???" ### default

        cu = currentunits1.to_s.split(".")[0].split(",")[0].split("'")[0].split("\"")[0].split("c")[0].split("m")[0]
        if cu == "120000"
           units = "inches"
        elsif cu == "10000"
           units = "feet"
        elsif cu == "3048000"
           units = "mm"
        elsif cu == "304800"
           units = "cm"
        elsif cu == "3048"
           units = "m"
        end #if   
        if currentunits2 == "1\""
           units = "inches or \" "
        end #if

        ### ---------------
        if units[-1,1] == "m" ### metric units so these are in metres
           xspacing = 50.mm
           xthickness = 3.mm
        else ### not metric so these are in feet
           xspacing = 2.inch
           xthickness = 0.125.inch
        end #if

        mlayers = Sketchup.active_model.layers
        layers = []
        mlayers.each{|e|layers.push e.name}
        dlayer = layers[0]
        layers = layers - [dlayer]
        layers.sort!

        #----------- sort possible special Layer...
        @sfix = Time::now.to_i.to_s[3..-1]
        layerSlice = ("SLCE-" + @sfix)
        @makelayer = "<Make New Layer>"
        layers = [dlayer] + [layerSlice] + [@makelayer] + layers
        layers.uniq!
        layers = layers.join('|')

        ### colours
        mcolours = Sketchup.active_model.materials
        colours = []
        mcolours.each{|e|colours.push e.name}
        colours.sort!
        colours = ["<Default>"] + colours + (Sketchup::Color.names.sort!)
        colours.uniq!
        colours = colours.join('|')

        ### get info
        title       = "SliceModeler Parameters"
        @spacing    = xspacing    if not @spacing
        @thickness  = xthickness  if not @thickness
        @layerSlice = dlayer      if not @layerSlice
        @material   = "<Default>" if not @material

        ### omit first chosen axis from next round
        if @nn == 2
	        if @xyz == "X"
               @xyz = "Y"
	           xyz = "Y|Z"
	        elsif @xyz == "Y"
               @xyz = "X"
	           xyz = "X|Z"
	        elsif @xyz == "Z"
               @xyz = "X"
	           xyz = "X|Y"
	        else
               @xyz = "X"
	           xyz = "X|Y|Z"
	        end #if
            prompts = ["Slice Spacing (" + units + "): ","Slice Orientation: ","Layer: ","Colour: "]
            types   = ["",xyz,layers,colours]
            values  = [@spacing,@xyz,@layerSlice,@material]
            popups  = ["",xyz,layers,colours]

	        results = inputbox( prompts, values, popups, title )
	        return nil if not results

	        ### do processing of results
	        @spacing    = results[0]
	        @xyz        = results[1]
	        @layerSlice = results[2]
	        @material   = results[3]
        else
            @xyx = "X"
	        xyz = "X|Y|Z"

            prompts = ["Slice Spacing (" + units + "): ","Slice Thickness (" + units + "): ","Slice Orientation: ","Layer: ","Colour: "]
            types   = ["","",xyz,layers,colours]
            values  = [@spacing,@thickness,@xyz,@layerSlice,@material]
            popups  = ["","",xyz,layers,colours]

	        results = inputbox( prompts, values, popups, title )
	        return nil if not results

	        ### do processing of results
	        @spacing    = results[0]
	        @thickness  = results[1]
	        @xyz        = results[2]
	        @layerSlice = results[3]
	        @material   = results[4]
        end #if

        ### check spacing
        if @spacing <= 0
           UI.beep
           UI.messagebox("Spacing CAN'T be <= 0 !  \nMade default = #{xspacing} #{units}")
           @spacing = xspacing
        end#if

        if @thickness > @spacing
           UI.beep
           if xthickness < @spacing
              UI.messagebox("Thickness CANNOT > Spacing !  \nThickness = default = #{xthickness} #{units}")
              @thickness = xthickness
           else
              UI.messagebox("Thickness CANNOT > Spacing !  \nThickness = Spacing = #{@spacing} #{units}")
              @thickness = @spacing
           end #if
        end #if

        ### make layer dialog
        if @layerSlice == @makelayer
           results2 = inputbox(["New Slices Layer Name: "],["????"],"New Layer Name")
           if results2
              @layerSlice = results2[0]
           else
              @layerSlice = nil ### default layer
              return nil ### cancel everything
           end #if
        end #if

        true
    end #def dialog

    ##################################################################################################
    #
    # --- Dialog to enter gap for flattened slices
    #
    ##################################################################################################
    def SliceModeler::gap

        ### get current units ----------------------
        currentunits1 = Sketchup.format_length 120000
        currentunits2 = Sketchup.format_length 1
        units = "???" ### default

        cu = currentunits1.to_s.split(".")[0].split(",")[0].split("'")[0].split("\"")[0].split("c")[0].split("m")[0]
        if cu == "120000"
           units = "inches"
        elsif cu == "10000"
           units = "feet"
        elsif cu == "3048000"
           units = "mm"
        elsif cu == "304800"
           units = "cm"
        elsif cu == "3048"
           units = "m"
        end #if   
        if currentunits2 == "1\""
           units = "inches or \" "
        end #if
        ### ---------------
        if units[-1,1] == "m" ### metric units so these are in mm
           xgap = 10.0.mm
        else ### not metric so these are in inches
           xgap = 0.5.inch
        end #if

        ### get info
        prompts = ["#{units}: "]
        types = [""]
        title = "Minimum Gap"
        @gap = xgap if not @gap
        values = [@gap]
        popups = [""]
        results = inputbox( prompts, values, popups, title )
        return nil if not results

        ### do processing of results
        @gap = results[0]

        true
    end #def gap

    ##################################################################################################
    #
    # --- Function for generating points on a circle
    #
    ##################################################################################################
    def SliceModeler::circle(center,normal,radius,numseg)

        # Get the x and y axes
        axes = Geom::Vector3d.new(normal).axes
        center = Geom::Point3d.new(center)
        xaxis = axes[0]
        yaxis = axes[1]
        xaxis.length = radius
        yaxis.length = radius

        # compute the points
        da = (Math::PI * 2) / numseg
        pts = []
        for i in 0...numseg do
            angle = i * da
            cosa = Math.cos(angle)
            sina = Math.sin(angle)
            vec = Geom::Vector3d.linear_combination(cosa,xaxis,sina,yaxis)
            pts.push(center + vec)
        end

        # close the circle
        pts.push(pts[0].clone)
        pts
    end #def

end #class
##################################################################################################


#--------- menu -----------------------------
if( not file_loaded?("SliceModeler.rb") )
   add_separator_to_menu("Plugins")###
   UI.menu("Plugins").add_item("SliceModeler") { SliceModeler.calculate }###
   ### $submenu5.add_item("SliceModeler") { SliceModeler.calculate }###
   ### remove ### in front of 6 lines below if to display in context-menu...
   ### UI.add_context_menu_handler do | menu |
   ###   if Sketchup.active_model.selection[0].typename == "Group" or Sketchup.active_model.selection[0].typename == "ComponentInstance"
   ###      menu.add_separator
   ###      menu.add_item("SliceModeler") { SliceModeler.calculate }
   ###   end #if ok
   ### end #do menu

end#if

file_loaded("SliceModeler.rb")
##################################################################################################
