=begin
  Copyright 2010-2014 (c), TIG
  All Rights Reserved.
  THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED 
  WARRANTIES,INCLUDING,WITHOUT LIMITATION,THE IMPLIED WARRANTIES OF 
  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
###
  latticeMaker.rb
###
Description:
  Takes a Selection of Faces and makes a Lattice, offset by a frame's given 
  width and depth, and pane inset, you can also assign limited Materials 
  to the frame/pane for ease of future selection/manipulation.
  It is similar to EEbyRailsToLattice BUT works on selected Surfaces/Faces...
###
Usage:
  Make a 3D Mesh - perhaps using EEbyRails or other tools - remember that 
  Faces need not be triangulated if all Edges' Vertices are 'coplanar', so 
  using similar profiles/rails will allow 'quad faces'.  You can also 
  manually draw over faces to sub-divide them as required.
  Edit the Group and Select the Faces to be converted into a Lattice - 
  any Edges etc in the Selection are ignored so you don't need to be too 
  careful.  Note that the Selected Faces and their Edges that are not 
  required by non-selected Faces will be deleted and replaced by the new 
  Lattice Group - if you want to keep them make a copy of the original mesh 
  to one side beforehand...
  Note that a Lattice frame is made in the direction away from a Face's 
  'front', so if you want the frames and pane to be formed in the other 
  direction 'Reverse' the Selected Faces beforehand.
  
  Run 'Lattice Maker', from the Plugins Menu.
  You are then prompted to chose the 'Lattice Properties':
'Width' - default is 50mm/2" - this is the width centered on the 
  lines so it's effectively a 100mm/4" 'frame' overall where faces abut.
  [if <=0 it defaults]
'Depth' - default is 50mm/2", this is measured 'in' from the face 
  [if==0 there is no 'depth', if <0 it defaults].
'Pane Inset' - the amount the pane is inset from the frame's top-face - 
  default is 25.mm/1" [if ==0 there is no 'inset', if <0 it defaults; 
  it can never be more than the depth, and reverts to that if it is].
'Pane Thickness' - the thickness of the pane measured inset from the 
  pane's top face - default is 5mm/0.25" [if ==0 the pane is 'one sided' 
  (facing 'out'), if <0 it is 'outset', this -ve 'outset' cannot be > the 
  pane_inset, the pane_thickness is limited to the frame_depth-pane_inset].
  It is best to give the pane a thickness if it is to be seen from both 
  sides and the lattice might be exported to a 3rd party renderer.
'Lattice Material' - default is <Default> - additional choices are 'Red', 
  'Orange', 'Yellow', 'Green', 'Blue', 'Violet', 'Black', 'White' or 'Gray'.
'Pane Material' - default is <None>** - additional choices are <Default>, 
  "Glass"***, 'Red', 'Orange', 'Yellow', 'Green', 'Blue', 'Violet', 
  'Black', 'White', 'Gray'.
  **Note: if Pane Material == <None> then the faces that would form them 
  become 'holes' in the final lattice.
  ***The 'Glass' material will be made if it doesn't exist - it is colored 
  'bluish-light-gray' with 30% opacity.
  
  The Faces' edges are now offset to suit, then pushpulled to suit and the 
  materials applied.
  Note that faces that are too small to have a 'pane' are made 'solid'.
  The operation is one-step undo-able.

Donations:
  Are welcome [by PayPal], please use 'TIGdonations.htm' in the 
  ../Plugins/TIGtools/ folder.
   
To Do:
  Add Toolbar ?
  
Version:
  1.0 20100511 First release.
  1.1 20101023 Pane 'thickness' option added.
               EN-US/FR/ES Lingvo files added in ../Plugins/TIGtools/ folder.
  1.2 20101027 ES lingvo updated by Defisto. Zip now contains deBabelizer.rb
  1.3 20130528 Optimized for v2013.
  1.4 20131206 Future-proofed.
  1.5 20140304 Future-proofed again.
  1.6 20140904 More future-proofing.
=end

require('sketchup.rb')
load('deBabelizer.rb')

class LatticeMaker

def db(string)
  dir=File.dirname(__FILE__)+"/TIGtools"
  toolname="latticeMaker" 
  locale=Sketchup.get_locale.upcase
  path=dir+"/"+toolname+locale+".lingvo"
  if not File.exist?(path)
    return string
  else
    deBabelizer(string,path)
  end 
end#def

def LatticeMaker::db(string)
  dir=File.dirname(__FILE__)+"/TIGtools"
  toolname="latticeMaker" 
  locale=Sketchup.get_locale.upcase
  path=dir+"/"+toolname+locale+".lingvo"
  if not File.exist?(path)
    return string
  else
    deBabelizer(string,path)
  end 
end#def
  
  def activate
    @model=Sketchup.active_model
    @ents=@model.active_entities
    @sel=@model.selection
    @sela=@sel.to_a
    @sel.clear
	if Sketchup.version.to_i > 6
		@model.start_operation((db("Lattice Maker")),true)
        ### 'false' is best to see results as UI/msgboxes...
	else
		@model.start_operation((db("Lattice Maker")))
	end
    ###
    self.offset_faces(@sela)
    ###
    @model.commit_operation
    self.deactivate()
    ###
  end#activate
  
  def deactivate(view=nil)
    ### view.invalidate if view ###
    Sketchup.send_action("selectSelectionTool:")
    return nil
  end#deactivate
  
  def face_offset(face=nil, dist=0)
    return nil if not face or not face.valid?
	return nil if (not ((dist.class==Fixnum || dist.class==Float || dist.class==Length) && dist!=0))
    pi=Math::PI
	verts=face.outer_loop.vertices
	pts=[]
	0.upto(verts.length-1) do |i|
		vec1=(verts[i].position-verts[i-(verts.length-1)].position).normalize
		vec2=(verts[i].position-verts[i-1].position).normalize
		vec3=(vec1+vec2).normalize
		if vec3.valid?
			ang=vec1.angle_between(vec2)/2
			ang=pi/2 if vec1.parallel?(vec2)
			vec3.length=dist/Math::sin(ang)
			t=Geom::Transformation.new(vec3)
			if pts.length > 0
				if not (vec2.parallel?(pts.last.vector_to(verts[i].position.transform(t))))
					t=Geom::Transformation.new(vec3.reverse)
				end#if
			end#if
			pts.push(verts[i].position.transform(t))
		end#if
	end#upto
	face.parent.entities.add_face(pts) if pts[2]
  end#face_offset
  
  def offset_faces(ents)
    @msg=(db("Lattice Properties"))
    Sketchup::set_status_text(@msg)
    if not ents
      return nil
    else
      titl=(db("Lattice Properties"))
      defa=(db("<Default>"))
      none=(db("<None>"))
      glas=(db("'Glass'"))
      mats1=defa+"|Red|Orange|Yellow|Green|Blue|Violet|Black|White|Gray|"
      mats2=defa+"|"+none+"|"+glas+"|Red|Orange|Yellow|Green|Blue|Violet|Black|White|Gray|"
      prom1=(db("Width: "))
      prom2=(db("Depth: "))
      prom3=(db("Pane Inset: "))
      prom3a=(db("Pane Thickness: "))
      prom4=(db("Lattice Material: "))
      prom5=(db("Pane Material: "))
      wid=2.inch if @model.options["UnitsOptions"]["LengthUnit"]<2
      dep=2.inch if @model.options["UnitsOptions"]["LengthUnit"]<2
      ins=1.inch if @model.options["UnitsOptions"]["LengthUnit"]<2
      thk=0.25.inch if @model.options["UnitsOptions"]["LengthUnit"]<2
      wid=50.mm if @model.options["UnitsOptions"]["LengthUnit"]>1
      dep=50.mm if @model.options["UnitsOptions"]["LengthUnit"]>1
      ins=25.mm if @model.options["UnitsOptions"]["LengthUnit"]>1
      thk=5.mm if @model.options["UnitsOptions"]["LengthUnit"]>1
      ###
      results=UI.inputbox([prom1,prom2,prom3,prom3a,prom4,prom5], [wid,dep,ins,thk,defa,none], ["","","","",mats1,mats2], titl)
      return nil if not results
      @msg=(db("Processing"))
      Sketchup::set_status_text(@msg)
      width=results[0]
      depth=results[1]
      pinset=results[2]
      pthick=results[3]
      frame=results[4]
      pane=results[5]
      ###
      width=wid if width<=0
      depth=dep if depth<0
      pinset=0.0 if pinset<0
      pinset=depth if pinset >depth
      pinset=depth if pane==none
      pthick=0 if pane==none
      if pthick <0
        if pthick.abs > pinset
          pthick= -pinset
        end#if
      end#if
      if pthick > depth-pinset
        pthick=depth-pinset
      end#if
      ###
      mats=[];@model.materials.each{|m|mats<< m.display_name}
      ### this traps for 'simlar' names matching...
      ### make missing materials.....................
      frame=nil if frame==defa
      if frame and not mats.include?(frame)
        mat=@model.materials.add(frame)
        mat.color=frame ### e.g. 'Gray'
      end#if
      pane=nil if pane==defa
      if pane==glas and not mats.include?(pane)
        mat=@model.materials.add(glas)
        mat.color=[150,200,250]
        mat.alpha=0.30
      elsif pane and pane != none and not mats.include?(pane)
        mat=@model.materials.add(pane)
        mat.color=pane ### e.g. 'Gray'
      end#if
      ###
      ofaces=[]; ents.to_a.each{|e|ofaces<< e if e.class==Sketchup::Face}
      return nil if not ofaces[0]
      entities=ofaces[0].parent.entities
      group=entities.add_group(ofaces)
      ents=group.entities 
      ofaces=[]; ents.to_a.each{|e|ofaces<< e if e.class==Sketchup::Face}
      ###
      ofaces.each{|face|self.face_offset(face, -(width.to_l))}
      pfaces=[]
      ents.to_a.each{|e|pfaces << e if e.class==Sketchup::Face and e.valid? and not ofaces.include?(e)}
      ### remove floating faces
      @msgx=@msg
      pfaces.each{|e|
        e.erase! if e.edges[0].faces.length==1
        @msgx=@msgx+"."
        Sketchup::set_status_text(@msgx)
      }
      Sketchup::set_status_text(@msg);@msgx=@msg
      ents.to_a.each{|e|
        e.erase! if e.class==Sketchup::Edge and e.faces.length==0
        @msgx=@msgx+"."
        Sketchup::set_status_text(@msgx)
      }
      ###
      pfaces=[]
      ents.to_a.each{|e|pfaces << e if e.class==Sketchup::Face and e.valid? and not ofaces.include?(e)}
      if depth>0
        tfaces=[]
        Sketchup::set_status_text(@msg);@msgx=@msg
        pfaces.each{|face|
          norm=face.normal
          allfaces=[]
          ents.to_a.each{|e|allfaces<< e if e.class==Sketchup::Face}
          face.pushpull(-(pinset.to_l))
          nowfaces=[]
          ents.to_a.each{|e|nowfaces<< e if e.class==Sketchup::Face}
          newfaces= nowfaces - allfaces
          newfaces.each{|e|tfaces<< e if e.valid? and (e.normal==norm or e.normal.reverse==norm)}
          tfaces[-1].reverse! if tfaces[-1].normal!=norm
          if pthick!=0
            tfaces[-1].pushpull(-(pthick.to_l),true)
            nowfaces=[]
            ents.to_a.each{|e|nowfaces<< e if e.class==Sketchup::Face}
            newfaces= nowfaces - allfaces
            tfaces[-1].reverse! if tfaces[-1].normal!=norm
            newfaces.each{|e|tfaces<< e if e.valid? and (e.normal==norm or e.normal.reverse==norm)}
            newfaces.each{|e|e.erase! if e.valid? and not (e.normal==norm or e.normal.reverse==norm)}
          end#if
          @msgx=@msgx+"."
          Sketchup::set_status_text(@msgx)
        }
        pfaces=tfaces
      end#if
      ### make '3d' with back
      Sketchup::set_status_text(@msg);@msgx=@msg
      ofaces.each{|face|
        face.pushpull(-(depth.to_l),true)
        face.reverse!
        @msgx=@msgx+"."
        Sketchup::set_status_text(@msgx)
      }
      ###
      faces=[]
      ents.to_a.each{|e|faces<< e if e.class==Sketchup::Face}
      ffaces= faces - pfaces
      Sketchup::set_status_text(@msg);@msgx=@msg
      ffaces.each{|e|
        e.material=frame if e.valid?
        @msgx=@msgx+"."
        Sketchup::set_status_text(@msgx)
      }
      if pane == none
        Sketchup::set_status_text(@msg);@msgx=@msg
        pfaces.each{|e|
          e.erase! if e.valid?
          @msgx=@msgx+"."
          Sketchup::set_status_text(@msgx)
        }
      else
        Sketchup::set_status_text(@msg);@msgx=@msg
        pfaces.each{|e|
          e.material=pane if e.valid?
          e.back_material=pane if e.valid? ###
          @msgx=@msgx+"."
          Sketchup::set_status_text(@msgx)
        }
      end#if
    end#if
    Sketchup::set_status_text("")
  end#offset_faces
    
end#class -----------------------------------

def latticeMaker()
  Sketchup.active_model.select_tool(LatticeMaker.new)
end

### menu ################
if not file_loaded?(__FILE__)
    textstring=LatticeMaker::db"Lattice Maker"
    instructions=LatticeMaker::db": Select Faces. Set Parameters. Makes a Lattice..."
    dir=File.dirname(__FILE__)+"/TIGtools"
    toolname="latticeMaker"
    locale=Sketchup.get_locale.upcase
    path=dir+"/"+toolname+locale+".lingvo"
    if File.exist?(path)
      textstring=deBabelizer(textstring,path)
      instructions=deBabelizer(instructions,path)
    end#if
    cmd=UI::Command.new(textstring){latticeMaker()}
    UI.menu("Plugins").add_item(textstring){latticeMaker()}
    cmd.status_bar_text=textstring+instructions
end
file_loaded(__FILE__)
