=begin
Copyright 2010-2013 (c) TIG
[Parts Based on TIG's 'Volume.rb' tools & some ideas from 
Alexander Schreyer's 'GetCentroid.rb']
  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.
  
###
  CofGravity.rb
###

Usage:

'Find C of G'

  This works out a 3D Shape's C of G and other properties...
  Select one 'Group*' or 'Group* of Groups*' [*or 'Component Instance'] 
  = the 'Shape'.
  From the Plugins sub-menu 'C of G...' run 'Find C of G'.
  [There will be error messages if it's not a suitable selection]
  It asks for the Shape's 'Density' and its Units, available 
  units are = kg/m3, tonnes/m3, g/cm3, tons/yd3, lbs/ft3.
  The defaults are 1000 and kg/m3 [the density of water].
  So for example, you'd enter 2323, kg/m3 for Sandstone.
  The entered Density and its Units are remembered for that Model and 
  thereafter they are used for the dialog until they are changed.
  It also asks for the Shape's 'ID' - the default is taken from the 
  selected Shape's 'name' - e.g. 'Beam3' - you can enter anything 
  you wish as an alternative.  If it's an unnamed Group its default is 
  'Group'.  If it's a component and the instance also has a name the 
  definition and instance names are combined - e.g. 'Beam3:Granite'
  It processes the Shape [this might take a while - please be patient...]
  It adds an overlaid element, named after the 'ID'; it also has a 
  description added.
  This element displays the Shape's ID, Volume, Density and Weight.
  It also indicates the Center of Gravity [CofG] & the 6 Axial 
  Suspension Points [SP].
  To see the CofG use Xray mode as it's often placed 'internally'.
  Do not edit the contents of a C of G element if it might be used later 
  in 'Composite' processes.
  If parts have different densities then you should group them 
  separately and process them in turn - you can work out the 
  'Composite C of G' later...
  
'Composite C of G'

  This works out the 'Composite C of G' of two or more already processed 
  'C of G' elements.
  It is important that they remain in their original relationships and 
  alignments - moving them will return inaccurate results.
  Note that these must have been made with the same units of density - 
  e.g. all in 'kg/m3': although the densities themselves may vary.
  Select two or more premade 'C of G' elements.
  From the Plugins sub-menu 'C of G...' run 'Composite C of G'.
  [There will be error messages if they're not suitable selections]
  It processes the selected C of Gs and then adds an overlaid element: 
  named 'CompoCofG' - its description includes the names of those 
  C of Gs that have been made composite.
  This displays the Combined Shapes' 'Composite Weight', it also 
  indicates the Combined Center of Gravity [CofG] & the 6 Combined 
  Axial Suspension Points [cSP].
  You can reuse a 'CompoCofG' in subsequent 'Composite C of G' 
  processes to find another combined C of G etc...
  
Note:
  The C of G element has a special component placed at its 'C of G' - 
  called 'CofG'.  Do not move, edit or delete it, as it might be used in 
  subsequent 'Composite C of G' processes.
  
###

Donations:
  Are welcome [by PayPal], please use 'TIGdonations.htm' in the 
  ../Plugins/TIGtools/ folder.
   
Version:
1.0 20100303 First Release.
1.1 20100303 Accuracy improved.  CompoCofG Description improved.
1.2 20100307 FR lingvo added by Pilou.
1.3 20100308 Missing require deB... added.
1.4 20100308 'calculate' becomes 'do_calculation'.
1.5 20100310 'composite' becomes 'do_composite'
			Reworked to suit all Macs as well as PC.
			ES lingvo added by Oxer.
1.6 20130209 Intersection gaps caused by tiny facets healed to improve accuracy.
			Volume is now exact if it's a solid or best approximation otherwise.
			Accuracy option added: default is 1%, note that making if lower will 
			dramtically slow things down, more that 5% could be inaccurate on 
			some complex forms.  The lingvo files have been updated to suit.
1.7 20131203 Future-proofed.
=end
#-----------------------------------------------------------------------
require('sketchup.rb')
load('deBabelizer.rb')
###
class CofGravity

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

def initialize(typo=1)
  case typo
    when 1
      self.do_calculation()
    when 2
      self.do_composite()
    else
      self.do_calculation()
  end#case
end#def

###---------------------------------------
def make_component()
  model=Sketchup.active_model
  defn=model.definitions.add(db("CofG"))
  defn.entities.add_line(ORIGIN,[0,0,6])
  defn.entities.add_line(ORIGIN,[0,0,-6])
  defn.entities.add_line(ORIGIN,[0,6,0])
  defn.entities.add_line(ORIGIN,[0,-6,0])
  defn.entities.add_line(ORIGIN,[6,0,0])
  defn.entities.add_line(ORIGIN,[-6,0,0])
  txt=defn.entities.add_text((db("CofG")),ORIGIN,[2,2,2])###############
  txt.arrow_type=2
  defn.description=(db("This is the Marker for 'CofG': DO NOT CHANGE!"))
  return defn
end#def

###---------------------------------------
def do_calculation()

if Sketchup.version.to_i < 5
     UI.messagebox(db("C of G: Sorry, this is Version >= 6 Tool !"))
     return nil
end#if

model=Sketchup.active_model
entities=model.active_entities

############
if Sketchup.version.to_i > 6
  model.start_operation((db("C of G")),true)
  ### 'false' is best to see results as UI/msgboxes...
else
  model.start_operation((db("C of G")))
end#if

ss=model.selection
view=model.active_view
  if ss.empty?
     UI.messagebox(db("C of G: NO Selection !"))
     return nil
  end#if
  if ss[1]
     UI.messagebox(db("C of G: Selection MUST be ONE Group or Component !"))
     return nil
  end#if
  if not ss[0].is_a?(Sketchup::Group) and not ss[0].is_a?(Sketchup::ComponentInstance)
     UI.messagebox(db("C of G: Selection is NOT a Group or Component !"))
     return nil
  end#if
###
selected=ss[0]
###
# -------------- set up @units and @percent in def dialog for later...
# -------------- make cutting disc face at xy bounds
###
bb=selected.bounds
xmin=bb.min.x
ymin=bb.min.y
xmax=bb.max.x
ymax=bb.max.y
zmin=bb.min.z
zmax=bb.max.z
###
if xmin == xmax or ymin == ymax or zmin == zmax
   UI.beep
   UI.messagebox(db("C of G: The Selection has no Volume !"))
   return nil
end#if
###
# ----------- dialog ----------------
### show VCB and status info
  Sketchup::set_status_text(db("C of G: Density..." ))
  Sketchup::set_status_text("",SB_VCB_LABEL)
  Sketchup::set_status_text("",SB_VCB_VALUE)
  ###
  return nil if not self.dialog(selected) ### do dialog...
  ###
###
#### ------- slice & z inc ------------------------------
###############################################
slice=((zmax-zmin)*@percent/100.0)### THE basic slice
increment_z=(zmax-zmin)/(((zmax-zmin)/slice).ceil)
###
apex=Geom.linear_combination(0.5,[xmin,ymin,zmax+20],0.5,[xmax,ymax,zmax+20])
### +2 puts it above top ( for text loc'n )
pnt1=[xmin,ymin,zmin] ### v1.1
pnt2=[xmax,ymax,zmin] ### v1.1
ctr=Geom.linear_combination(0.5,pnt1,0.5,pnt2)
rad=(pnt1.distance pnt2)### make a LOT bigger than bb ###v1.3
###
disc=entities.add_group
discentities=disc.entities
disccircle=discentities.add_circle(ctr,[0,0,1],rad,12)
discentities.add_face(disccircle)
disc.hidden=true
###
#--------------------------- reset Z max and min's
zmin=zmin+(increment_z/2)
###
# ------------------------ do each of slice
cofg=entities.add_group()
cofg.name="CofG"
volentities=cofg.entities
###
### 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)
### 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)
### entities2 - an entities object (or an array of entities ?)
###
### loop through all possible slices - bottom up
n=((zmax-zmin)/slice).ceil.to_i
c=0
z=increment_z/2 ### move to start
while z < zmax-zmin+increment_z
### show VCB and status info
  Sketchup::set_status_text((db("C of G: Step "))+c.to_s+(db(" of "))+n.to_s)
  Sketchup::set_status_text("",SB_VCB_LABEL)
  Sketchup::set_status_text("",SB_VCB_VALUE)
### do cut
  disc.move! Geom::Transformation.new([0,0,z]) ### first placing near bottom v1.6
  eds=discentities.intersect_with(true, disc.transformation, volentities, cofg.transformation, true, selected)
  ### heal holes
  max=3.mm ### ?
  leds=eds.grep(Sketchup::Edge).select{|e|e.start.edges.length==1 || e.end.edges.length==1}
  leds.each{|e|
	di=1000000000
	if e.start.edges.length==1
		vp=nil
		(leds-[e]).each{|ee|
			ee.vertices.each{|v|
				next if v.edges.length>1
				d=v.position.distance(e.start.position)
				next if d>max
				if d<di
					vp=v.position
					di=d
				end
			}
		}
		if vp
			volentities.add_line(e.start.position, vp)
		end
	end
	if e.end.edges.length==1
		vp=nil
		(leds-[e]).each{|ee|
			ee.vertices.each{|v|
				next if v.edges.length>1
				d=v.position.distance(e.end.position)
				next if d>max
				if d<di
					vp=v.position
					di=d
				end
			}
		}
		if vp
			volentities.add_line(e.end.position, vp)
		end
	end
  }
  ###
  z=z+increment_z
  c=c+1
  #view.refresh
end#while
### delete disc
disc.erase!
#view.refresh
msg=(db("C of G: Calculating"))
Sketchup::set_status_text(msg)
Sketchup::set_status_text("",SB_VCB_LABEL)
Sketchup::set_status_text("",SB_VCB_VALUE)
### face the slices
volentities.grep(Sketchup::Edge).each{|e|
  e.find_faces
  msg=msg+"."
  Sketchup::set_status_text(msg)
}
msg=(db("C of G: Calculating"))+" ."
Sketchup::set_status_text(msg)
faces=volentities.grep(Sketchup::Face)
faces.each{|face|
  face.reverse! if face.normal.z==-1
  msg=msg+"."
  Sketchup::set_status_text(msg)
}### now all face up
#return nil
### 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 find C of G's etc
### show VCB and status info
Sketchup::set_status_text("")
Sketchup::set_status_text("",SB_VCB_LABEL)
Sketchup::set_status_text("",SB_VCB_VALUE)
###
msg=(db("C of G: Calculating"))
area=0
volentities.to_a.each{|f|
  area=(area+f.area)if f.is_a?(Sketchup::Face)
  msg=msg+"."
  Sketchup::set_status_text(msg)
}
###
if area==0
   UI.messagebox(db("C of G: There is NO Volume !"))
   cofg.erase! if cofg.valid?
   return nil
end#if
# --------------- get volume ----
###
begin
	if selected.manifold?
		volume=selected.volume ### it's a solid
	else
		volume=(area*increment_z)### in cubic inches
	end
rescue
	volume=(area*increment_z)### in cubic inches
end
###
### convert it to units matching density...
volume=(volume* 0.000016387064 *1000).round.to_f/1000 if @units=~/\/m3/
volume=(volume* 16.387064).round.to_f if @units=~/\/cm3/
volume=(volume* 0.000021433471 *1000).round.to_f/1000 if @units=~/\/yd3/
volume=(volume* 0.000578703704 *100).round.to_f/100 if @units=~/\/ft3/
volumeTxt=volume.to_s
volumeTxt=volume.to_s+" m3" if @units=~/\/m3/
volumeTxt=volume.to_s+" cm3" if @units=~/\/cm3/
volumeTxt=volume.to_s+" yd3" if @units=~/\/yd3/
volumeTxt=volume.to_s+" ft3" if @units=~/\/ft3/
###
volumeText=(db("Volume="))+volumeTxt+"\n"
###
densityTxt=@density.to_s+" "+@units
densityText=(db("Density="))+densityTxt+"\n"
###
weight=(volume*@density *1000).round.to_f/1000
wunits=""
wunits=" g" if @units=~/^g\//
wunits=" kg" if @units=~/^kg\//
wunits=" tonnes" if @units=~/^tonnes\//
wunits=" lbs" if @units=~/^lbs\//
wunits=" tons" if @units=~/^tons\//
weightTxt=weight.to_s+wunits
weightText=(db("Weight="))+weightTxt+"\n"
###
idText="CofG:"+@id+"\n"
###
allText=idText+volumeText+densityText+weightText
### add text
volentities.add_text(allText,apex)
###
### NOW we get C of G centroids
Sketchup::set_status_text(db("C of G: Calculating Centroids..."))
Sketchup::set_status_text("",SB_VCB_LABEL)
Sketchup::set_status_text("",SB_VCB_VALUE)

faces=volentities.grep(Sketchup::Face)

faces.each{|face|
  if face.loops.length>1
    tgp=volentities.add_group()
    tgp.entities.fill_from_mesh(face.mesh(3),true,0)
    tfaces=tgp.entities.grep(Sketchup::Face)
    face.erase!
    tfaces.each{|f|
      pts=[]
      f.vertices.each{|v|pts<<v.position}
      volentities.add_face(pts)
    }
    tgp.erase!
  end#if
}
msg=(db("C of G: Calculating"))
faces=[]
volentities.to_a.each{|e|
  faces<< e if e.is_a?(Sketchup::Face)
  msg=msg+"."
  Sketchup::set_status_text(msg)
}### all donuts triangulated so centroids easy...
### centroids
msg=(db("C of G: Calculating"))
cents=[]
faces.each{|face|
  data=[]
  face.reverse! if face.normal==[0,0,-1]
  cent=self.find_centroid(face)
  data=[cent,face.area]
  cents<< data
  msg=msg+"."
  Sketchup::set_status_text(msg)
}
msg=(db("C of G: Calculating"))
cent0=cents[0][0]
weight0=cents[0][1]
1.upto(cents.length-1)do |i|
  cent=cents[i][0]
  cweight=cents[i][1]
  cent0=cent0.clone.offset((cent0.vector_to(cent)),(cent0.distance(cent))*cweight/(weight0+cweight))
  weight0=weight0+cweight
  msg=msg+"."
  Sketchup::set_status_text(msg)
end#do
###
msg=(db("C of G: Calculating"))
cog=cent0
### remove slice faces
volentities.to_a.each{|e|
  e.erase! if e.is_a?(Sketchup::Edge) and e.valid?
  msg=msg+"."
  Sketchup::set_status_text(msg)
}
### add 3D cross
if not model.definitions["CofG"]
  cogcompo=self.make_component()
else
  cogcompo=model.definitions["CofG"]
end#if
tr=Geom::Transformation.new(cog)
coginst=volentities.add_instance(cogcompo,tr)
###
### now asp's
minX=[xmin,cog.y,cog.z]
maxX=[xmax,cog.y,cog.z]
minY=[cog.x,ymin,cog.z]
maxY=[cog.x,ymax,cog.z]
minZ=[cog.x,cog.y,zmin]
maxZ=[cog.x,cog.y,zmax]
asp_maxZ=volentities.add_line(maxZ,[maxZ.x,maxZ.y,maxZ.z+6])
asp_minZ=volentities.add_line(minZ,[minZ.x,minZ.y,minZ.z-6])
asp_maxY=volentities.add_line(maxY,[maxY.x,maxY.y+6,maxY.z])
asp_minY=volentities.add_line(minY,[minY.x,minY.y-6,minY.z])
asp_maxX=volentities.add_line(maxX,[maxX.x+6,maxX.y,maxX.z])
asp_minX=volentities.add_line(minX,[minX.x-6,minX.y,minX.z])
### adjust if misses surface
edgs=[asp_maxZ,asp_minZ,asp_maxY,asp_minY,asp_maxX,asp_minX]
edgs.each{|edg|
  v0=edg.vertices[0]
  v1=edg.vertices[1]
  p0=v0.position
  p1=v1.position
  vec=p1.vector_to(p0)
  raytest=model.raytest(p0,vec)
  pt=raytest[0]
  volentities.add_line(p0,pt)if raytest[1][-1].is_a?(Sketchup::Face)
  txt=volentities.add_text((db("SP")),p1)
}
###############################################
cogLayer=(db("CofG"))
cogLayer=model.layers.add(cogLayer)
cofg.layer=cogLayer
###
cofg.name=(db("CofG"))+":"+@id #############################
cofg.description=(db("C of G for "))+@id
cofg.set_attribute("CofG","tag",true)
###
coginst.set_attribute("CofG","volume",volume)
coginst.set_attribute("CofG","weight",weight)
coginst.set_attribute("CofG","id",@id)
###
### done
### show VCB and status info
  Sketchup::set_status_text("")
  Sketchup::set_status_text("",SB_VCB_LABEL)
  Sketchup::set_status_text("",SB_VCB_VALUE)
### unselected...
model.selection.clear
###
### update view
view.invalidate
# ---------------------- Close/commit group
model.commit_operation
#-----------------------
end#def


###################################################---------------------
def find_centroid(face)
    vertices=face.vertices
    vertices[vertices.length]=vertices[0] ### =loop
    centroid=Geom::Point3d.new
    a_sum=0.0; x_sum=0.0; y_sum=0.0
   for i in (0...vertices.length-1)
      temp=(vertices[i].position.x*vertices[i+1].position.y)-(vertices[i+1].position.x*vertices[i].position.y)
      a_sum+=temp
      x_sum+=(vertices[i+1].position.x+vertices[i].position.x)*temp
      y_sum+=(vertices[i+1].position.y+vertices[i].position.y)*temp
    end#for
    area=a_sum/2
    centroid.x=x_sum/(3*a_sum)
    centroid.y=y_sum/(3*a_sum)
    centroid.z=face.bounds.min.z
    return centroid
end#def find_centroid

###################################################---------------------
def dialog(selected)
   ### get id, units and accuracy etc
   model=Sketchup.active_model
   name=selected.name
   if not name or name==""
     if selected.is_a?(Sketchup::ComponentInstance)
       name=selected.definition.name
     else
       name="Group"
     end#if
   else
     if selected.is_a?(Sketchup::ComponentInstance)
       name=selected.definition.name+":"+selected.name
     else
       name=selected.name
     end#if
   end#if
   @id=name
   ###
   @density=model.get_attribute("CofG","density",1000.0)
   @units=model.get_attribute("CofG","units","kg/m3")
   @percent=model.get_attribute("CofG","percent",1.0)
   units=["kg/m3", "tonnes/m3", "g/cm3", "tons/yd3", "lbs/ft3"].join('|')
   prompt0=(db("Density: "))
   prompt1=(db("Units: "))
   prompt2=(db("ID: "))
   prompt3=(db("Accuracy"))+" %: "
   prompts=[prompt0,prompt1,prompt2,prompt3]
   title=(db("C of G Info"))
   values=[@density, @units, @id, @percent]
   popups=["",units,"",""]
   results = inputbox(prompts,values,popups,title)
   return nil unless results
### do processing of results
@density, @units, @id, @percent = results
###
model.set_attribute("CofG","density",@density)
model.set_attribute("CofG","units",@units)
model.set_attribute("CofG","percent",@percent)
###
return true
###
end #def dialog

###################################################---------------------
def do_composite()
  model=Sketchup.active_model
  entities=model.active_entities
  ###
  Sketchup::set_status_text("C of G: Composite C of G...")
  Sketchup::set_status_text("",SB_VCB_LABEL)
  Sketchup::set_status_text("",SB_VCB_VALUE)
  ############
if Sketchup.version[0,1].to_i > 6
  model.start_operation((db("C of G")),true)
  ### 'false' is best to see results as UI/msgboxes...
else
  model.start_operation((db("C of G")))
end#if

ss=model.selection
view=model.active_view
  if ss.empty?
     UI.messagebox(db("C of G: NO Selection !"))
     return nil
  end#if
  if not ss[1]
     UI.messagebox(db("C of G: Selection MUST be TWO or more 'C of G' Groups !"))
     return nil
  end#if
  if not ss[0].is_a?(Sketchup::Group) or not ss[1].is_a?(Sketchup::Group)
     UI.messagebox(db("C of G: Selection is NOT a Group !"))
     return nil
  end#if
  gp0=ss[0]
  gp1=ss[1]
  isCoG=0
  isCoG+=1 if not gp0.get_attribute("CofG","tag",nil)
  isCoG+=1 if not gp1.get_attribute("CofG","tag",nil)
  if isCoG>0
     UI.messagebox(db("C of G: Selection NOT 2 'C of G' Groups !"))
     return nil
  end#if
  ### is OK ? so process
  sel=ss.to_a
  ###
  togo=[]
  todo=[]
  sel.each{|group|
    tagged=group.get_attribute("CofG","tag",nil)
    togo<< group if not tagged
    todo<< group if tagged
  }
#############################################
### add markers at cog etc
tgp=entities.add_group(todo)
cog_gp=tgp.copy
####
tgp.explode ### back as it was
###
ents=cog_gp.entities
ents.to_a.each{|e|e.explode}
ents.to_a.each{|e|e.erase! if e.valid? and not e.is_a?(Sketchup::Edge)}
cog_gp_tr=cog_gp.transformation
###
ss.clear
ss.add(cog_gp)
###
bb=cog_gp.bounds
xmin=bb.min.x
ymin=bb.min.y
xmax=bb.max.x
ymax=bb.max.y
zmin=bb.min.z
zmax=bb.max.z
###
ents.to_a.each{|e|e.erase! if e.valid?}
### find cogs and cog
cogs=[]
todo.each{|group|
  group.entities.each{|e|
    if e.is_a?(Sketchup::ComponentInstance) and e.definition.name==(db("CofG"))
      cogs<< e
    end#if
  }
}
###
msg=(db("C of G: Calculating"))
cents=[]
cogs.each{|inst|
  data=[]
  gp=inst.parent.instances[0]
  cent=(inst.transformation.origin).transform!(gp.transformation)
  wt=inst.get_attribute("CofG","weight",0.0)
  vol=inst.get_attribute("CofG","volume",0.0)
  data=[cent,wt,vol]
  cents<< data
  msg=msg+"."
  Sketchup::set_status_text(msg)
}
msg=(db("C of G: Calculating"))
cent0=cents[0][0]
weight0=cents[0][1]
volume0=cents[0][2]
1.upto(cents.length-1)do |i|
  cent=cents[i][0]
  cweight=cents[i][1]
  cvolume=cents[i][2]
  cent0=cent0.clone.offset((cent0.vector_to(cent)),(cent0.distance(cent))*cweight/(weight0+cweight))
  weight0=weight0+cweight
  volume0=volume0+cvolume
  msg=msg+"."
  Sketchup::set_status_text(msg)
end#do
###
weight=weight0
volume=volume0
cog=cent0
###
### add 3D cross
if not model.definitions[(db("CofG"))]
  cogcompo=self.make_component()
else
  cogcompo=model.definitions[(db("CofG"))]
end#if
tr=Geom::Transformation.new(cog)
coginst=ents.add_instance(cogcompo,tr)
###
### add asp's
### now asp's
minX=[xmin,cog.y,cog.z]
maxX=[xmax,cog.y,cog.z]
minY=[cog.x,ymin,cog.z]
maxY=[cog.x,ymax,cog.z]
minZ=[cog.x,cog.y,zmin]
maxZ=[cog.x,cog.y,zmax]
asp_maxZ=ents.add_line(maxZ,[maxZ.x,maxZ.y,maxZ.z+6])
asp_minZ=ents.add_line(minZ,[minZ.x,minZ.y,minZ.z-6])
asp_maxY=ents.add_line(maxY,[maxY.x,maxY.y+6,maxY.z])
asp_minY=ents.add_line(minY,[minY.x,minY.y-6,minY.z])
asp_maxX=ents.add_line(maxX,[maxX.x+6,maxX.y,maxX.z])
asp_minX=ents.add_line(minX,[minX.x-6,minX.y,minX.z])
###
### adjust if misses surface
edgs=[asp_maxZ,asp_minZ,asp_maxY,asp_minY,asp_maxX,asp_minX]
edgs.each{|edg|
  v0=edg.vertices[0]
  v1=edg.vertices[1]
  p0=v0.position
  p1=v1.position
  vec=p1.vector_to(p0)
  raytest=model.raytest(p0,vec)
 if raytest
  pt=raytest[0]
  ents.add_line(p0,pt)if raytest[1][-1].is_a?(Sketchup::Face)
 end#if
  txt=ents.add_text((db("cSP")),p1)
}
###
apex=Geom.linear_combination(0.5,[xmin,ymin,zmax+24],0.5,[xmax,ymax,zmax+24])
wtxt=nil
weightunits=""
todo[0].entities.each{|e|
  if e.is_a?(Sketchup::Text) and e.text=~/=/
    wtxt=e.text
    break
  end#if
}
if wtxt
  weightunits=wtxt.split(" ")[-1]
  weightunits=" "+weightunits
end#if
weightTxt=(db("Weight="))+weight.to_s+weightunits
allText=(db("CofG Composite"))+":\n"+weightTxt
ents.add_text(allText,apex)
### put into right place ???
cog_gp.transform!(cog_gp_tr.inverse)
###############################################
cogLayer=(db("CofG"))
cogLayer=model.layers.add(cogLayer)
cog_gp.layer=cogLayer
###
cog_gp.set_attribute("CofG","tag",true)
###
coginst.set_attribute("CofG","volume",volume)
coginst.set_attribute("CofG","weight",weight)
coginst.set_attribute("CofG","id",(db("CofG Composite")))
###
cog_gp.name="CompoCofG" #############################
desc=(db("Composite C of G"))+"\n"
names=""
todoIns=[]
todo.each{|e|e.entities.each{|ee|todoIns<< ee if ee.is_a?(Sketchup::ComponentInstance)}}
todoIns.each{|e|names=names+e.get_attribute("CofG","id","")+"\n"}
cog_gp.description=desc+names
###
### done
### show VCB and status info
  Sketchup::set_status_text("")
  Sketchup::set_status_text("",SB_VCB_LABEL)
  Sketchup::set_status_text("",SB_VCB_VALUE)
###
  ss.clear
  model.commit_operation
  ###
end#def
###########################
#-----------------------

end#class

#--------- menu -----------------------------
unless file_loaded?(File.basename(__FILE__))
   txt0="C of G..."
   txt1="Find C of G"
   txt2="Composite C of G"
   dir=File.dirname(__FILE__)+"/TIGtools"
   toolname="CofGravity"
   locale=Sketchup.get_locale.upcase
   path=dir+"/"+toolname+locale+".lingvo"
   if File.exist?(path)
     txt0=deBabelizer(txt0,path)
     txt1=deBabelizer(txt1,path)
     txt2=deBabelizer(txt2,path)
   end#if
   submenu=UI.menu("Plugins").add_submenu(txt0)
   submenu.add_item(txt1){CofGravity.new(1)}
   submenu.add_item(txt2){CofGravity.new(2)}
end#if
file_loaded(File.basename(__FILE__))
#---------------------------------
