# Copyright 2006, TIG
#
# 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.
#
#-----------------------------------------------------------------------
# Name        : Latticeizer
#
# Description : A tool to make a lattice from selected linework.
#				Options for width, depth and materials.
#				
# Menu Item   : Plugins -> Lattice-izer
# Context Menu: Lattice-izer (only available with suitable selection)
#
# Author      : TIG
#
# Usage       : Draw an outline of edges to make a lattice - e.g. truss, 
#				window.  The edges should be faced.  All faces can 
#				become panes, if some are made that are unwanted then 
#				erase them in a group-edit afterwards.
#				Select the edges and Group them.  This only works on a 
#				group - you can always explode the group afterwards.  
#				The group's edges will form the lattice's centre-lines. 
#				Pick Lattice-izer from the menu.  A dialog asks for the 
#				lattice's width and depth (default is 50mm/2"), and the 
#				materials for the lattice and any 'panes': also if you 
#				want to keep the original outline - Yes/No.
#				Defaults are <Default> and <No-Pane>, selecting a pane 
#				material (e.g. Glass) turns an open lattice such as a 
#				truss into a window...
#				The Offsets are made and faces PushPulled etc, the 
#				materials are added.
#				Extraneous lines are then erased if you said Yes.
#				It only works on 'outer-loops' so make NO islands etc.
#				Also no 'loose edges': edit lattice later to make these.
#			Known bugs: Inconsistency in output - occasional crashes, 
#				sometimes unexpected erasures or face making etc - 
#				undo+retry sometimes fixes it or try redrawing an edge.  
#				Split large outlines into smaller sub-groups.
#				Also draw the lattice outline in one plane only...
#				If you scale the group and then lattice-ize it then its 
#				lattice widths scale in proportion, e.g. scale a square 
#				outline group x 2 in one axis and apply 50mm widths, it 
#				gives unscaled edges are 50mm but in the scaled 
#				directions they are 100mm: if that isn't wanted then 
#				you need to explode it, then scale the edges/faces and 
#				then re-group before lattice-izing...
#		
# Type        : Tool
#
# Verison     :	1.0	25/06/06	First issue.
#				1.1 26/06/06	Original & extraneous lines erased.
#				1.2 27/06/06	Rewrite.  Asks if retain original edges.
#				1.3 28/06/06	RickW's latest Offset[TIG] included.
#				1.4 16/07/05	Perimeter check to avoid wrong offsets.
#								The Context Menu Item added.
#				1.5	16/07/06	Perimeter check fixed (?).
#				1.6 24/08/06	0 width trapped.
#       1.7 09/01/07  v6 .options["UnitsOptions"]["LengthUnit"] !!!
#       1.8 12/07/09  Reviewed and general update.
#-----------------------------------------------------------------------
require 'sketchup.rb'
### This Class (Face.offset...) is 'borrowed' from RickW's
### Windowizer and Offset scripts... and TIG tweaked...
class Sketchup::Face
	def offsetTIG(dist)
		pii=Math::PI
		if (not ((dist.class==Fixnum || dist.class==Float || dist.class==Length) && dist!=0))
		   return nil
		end#if
		verts=self.outer_loop.vertices
		perim=0
		pts1=[]
		pts2=[]
		0.upto(verts.length-1) do |a|
		    perim=perim+(verts[a].position.distance verts[a+1].position) if verts[a] and verts[a+1]
			vec1=(verts[a].position-verts[a-(verts.length-1)].position).normalize
			vec2=(verts[a].position-verts[a-1].position).normalize
			vec3=(vec1+vec2).normalize
			if vec3.valid?
				ang=vec1.angle_between(vec2)/2
				ang=pii/2 if vec1.parallel?(vec2)
				vec3.length=dist/Math::sin(ang)
				t=Geom::Transformation.new(vec3)
				if pts1.length > 0
					if not (vec2.parallel?(pts1.last.vector_to(verts[a].position.transform(t))))
						t=Geom::Transformation.new(vec3.reverse)
					end#if
				end#if
				pts1.push(verts[a].position.transform(t))
			end#if
			if vec3.valid?
				ang=pii-(vec1.angle_between(vec2)/2)####### v1.5
				ang=pii/2 if vec1.parallel?(vec2)
				vec3.length=dist/Math::sin(ang)
				t=Geom::Transformation.new(vec3)
				if pts2.length > 0
					if not (vec2.parallel?(pts2.last.vector_to(verts[a].position.transform(t))))
						t=Geom::Transformation.new(vec3.reverse)
					end#if
				end##if
				pts2.push(verts[a].position.transform(t))
			end#if
		end#upto
		perim=perim+(verts[0].position.distance verts.last.position) if verts[0] and verts.last
		### length of original face's loop perimter
		### now get perimeter of pts1 and pts2...
		perim1=0
		0.upto(pts1.length-1) do |a|
		    perim1=perim1+(pts1[a].distance(pts1[a+1])) if pts1[a] and pts1[a+1]
		end#upto
		perim1=perim1+(pts1[0].distance(pts1.last)) if pts1[0] and pts1.last
		###length of pts1 loop perimter
		perim2=0
		0.upto(pts2.length-1) do |a|
		    perim2=perim2+(pts2[a].distance(pts2[a+1])) if pts2[a] and pts2[a+1]
		end#upto
		perim2=perim2+(pts2[0].distance(pts2.last)) if pts2[0] and pts2.last
		###length of pts2 loop perimter
		perimMax=[]
		perimMin=[]
		if perim1 > perim2
		   perimMax=pts1
		   perimMin=pts2		   
		else
		   perimMax=pts2
		   perimMin=pts1
		end#if
		if dist>0
		   pts=perimMax
		else
		   pts=perimMin
		end#if
		###
		parent.entities.add_face(pts)
	end#def
end#class
###
###
class Lattice
###---------------------------------------
def Lattice::make
###
  model = Sketchup.active_model
  model.start_operation "Lattice"
  ss = model.selection
  if ss.empty?
     UI.beep
     UI.messagebox("NO Selection !")
     return nil
  end#if
  if ss[0].typename != "Group"
     UI.beep
     UI.messagebox("Selection is NOT a Group !")
     return nil
  end#if
  if ss[1]
     UI.beep
     UI.messagebox("Selection MUST be just ONE Group !")
     return nil
  end#if
###
group=ss[0]
### get materials (add frame and glass as 'Windowizer' defaults)
mats=model.materials
if mats["DimGray"]==nil
  frame=mats.add("DimGray")
  frame.color="DimGray"
end#if
if mats["BlueGlass"]==nil
  pane=mats.add("BlueGlass")
  pane.color=[162,163,196]
  pane.alpha=0.6
end#if
matNames1=["<Default>"]
mats.each {|e| matNames1.push(e.name)}
matList1=matNames1.join('|')
matNames2=["<No-Pane>","<Default>"]
mats.each {|e| matNames2.push(e.name)}
matList2=matNames2.join('|')
### dialog
@width=2.inch if not defined? @width and model.options["UnitsOptions"]["LengthUnit"]<2
@width=50.mm if not defined? @width and model.options["UnitsOptions"]["LengthUnit"]>1
@depth=2.inch if not defined? @depth and model.options["UnitsOptions"]["LengthUnit"]<2
@depth=50.mm if not defined? @depth and model.options["UnitsOptions"]["LengthUnit"]>1
units="inches" if model.options["UnitsOptions"]["LengthUnit"]==0
units="feet" if model.options["UnitsOptions"]["LengthUnit"]==1
units="mm" if model.options["UnitsOptions"]["LengthUnit"]==2
units="cm" if model.options["UnitsOptions"]["LengthUnit"]==3
units="m" if model.options["UnitsOptions"]["LengthUnit"]==4
@frame = "<Default>" if not defined? @frame
@pane = "<No-Pane>" if not defined? @pane
@keep = "No" if not defined? @keep
enums = ['','',matList1,matList2,"Yes|No"]
prompts = ["Lattice Width (#{units}):  ","Lattice Depth (#{units}):  ","Lattice Colour:  ","Pane Colour:  ","Keep Original Outline ?  "]
values = [@width,@depth,@frame,@pane,@keep]
results = inputbox prompts, values, enums, "Lattice Settings "
return nil if not results
width,depth,frame,pane,keep = results
if width==0
   UI.messagebox("Width cannot = 0\n Reverting to default (#{@width})")
   width=@width
end#if
@width,@depth,@frame,@pane,@keep=width,depth,frame,pane,keep
frame=nil if frame=="<Default>"
pane=nil if pane=="<Default>"
### do lattice
entities=group.entities
### get all edges
edges=[]
for e in entities
  if e.kind_of?(Sketchup::Edge) and e.valid?
    edges.push(e)
  end#if
end#for
oedges=edges
###
perims=[]###outer edges
for e in oedges
  perims.push(e) if e.faces[0] and not e.faces[1]
end#for
pedges=[]### list outer edges vertices
for p in perims
  pedges.push([p.start,p.end])
end#for
###
faces=[]
for e in entities
  faces.push(e) if e.typename=="Face"
end#for
for f in faces
  f.offsetTIG(width/-2)
end#for
###get outer edges faced
###make temp group
tgroup=entities.add_group
tentities=tgroup.entities
fedges=[]
for p in pedges
  n=tentities.add_line(p[0].position,p[1].position)
  fedges.push(n)
end#for
tface=tentities.add_face(fedges)
nface=tface.offsetTIG(width/2) if tface
tface.erase! if tface
tgroup.explode
### remove original lines v1.1 Y/N? in v1.2
if keep=="No"
  for e in oedges+fedges
    e.erase! if e.valid?
  end#for
end#if
### get all current faces
faces=[]
for e in entities
  faces.push(e) if e.typename=="Face"
end#for
### orient starting faces to first one found
norm=faces[0].normal
for f in faces
  f.reverse! if f.normal != norm
end#for
### get panes (only have an outer loop)
panes=[]
for f in faces
  panes.push(f) if not f.loops[1]###inner pane
end#for
###
faces=[]
for e in entities
  faces.push(e) if e.typename=="Face"
end#for
for f in faces-panes
  f.reverse! if f.valid?
  f.pushpull(depth/-2,true) if f.valid?###
  f.reverse! if f.valid?
  f.pushpull(depth/2,false) if f.valid?###
end#for
###
for p in panes
  p.material=nil
  p.back_material=nil
  if pane=="<No-Pane>"
    p.erase!
    panes=panes-[p]
  else
    p.material=pane
    p.back_material=pane
  end#if
  faces=faces-[p]
end#for
faces=[]
for e in entities
  faces.push(e) if e.typename=="Face"
end#for
for f in faces-panes
  f.material=frame
  f.back_material=frame
end#for
### erase extraneous lines etc ### v1.1
edges=[]
for e in entities
  if e.typename=="Edge"
    if (e.faces.length==2) and (e.faces[0].normal==e.faces[1].normal)
    ### coplanar
	  edges.push(e)
    end#if 
  end#if
end#for
for e in edges
  e.erase! if e.valid?
end#for
###
edges=[]
for e in entities
  if e.typename=="Edge"
    edges.push(e) if e.faces.length==0
    ### faceless
  end#if
end#for
for e in edges
  e.erase! if e.valid?
end#for
###
model.selection.clear
model.commit_operation
###
end#def
def Lattice::check
  ss = Sketchup.active_model.selection
  if ss.empty? or ss[0].typename != "Group" or ss[1]
     return nil
  else
     return true
  end#if
end#def
###
end#class
### Menus
if( not file_loaded?("Latticeizer.rb") )###
   UI.menu("Plugins").add_item("Lattice-izer") { Lattice::make }
   ###$submenu5.add_item("Lattice-izer") { Lattice::make }
### Context menu
   UI.add_context_menu_handler do | menu |
      if Lattice::check
         menu.add_separator
         menu.add_item("Lattice-izer") { Lattice::make }
      end#if
   end #do menu
end#if
#-----------------------------------------------------------------------------
file_loaded("Latticeizer.rb")###
###