# Name :          SmartDrop 1.4
# Modified by :	a4chitect
# Modif. notes :	works even after using the TBD's Randor (different scales and rotations of entities)
# Credits :			TBD for the drop.rb and further inspiration randor.rb, cphilips for his small trick http://groups.google.com/group/sketchupruby/msg/caaf0e9554881c5a and the SU community
# Description :   drop group/component on selected level (change in Y)
# Original Author :        TBD
# Usage :         1. select groups and/or components, right click, drop at intersection
#                   or
#                 1. select face, right click, get position to set level
#                 2. select groups and/or components, right click, drop at ...
# Date :          22.Jul.2oo4
# Type :          tool
# History:        1.5 (22.Jul.2oo4) - drop selected items at intersection instead of level
#                 1.4 (20.Jul.2oo4) - added debug construction line for drop at intersection
#                                   - bugfixed get position
#                 1.3 (19.Jul.2oo4) - added "find intersection"
#                 1.2 (14.Jul.2oo4) - added support for units
#                 1.1 (14.Jul.2oo4) - added "Drop at ..."
#                 1.o (14.Jul.2oo4) - first version
### v1.5x TIG fixed group.definition débâcle ! 20141124

require 'sketchup.rb'

class Sketchup::Group
	unless Sketchup::Group.method_defined?(:definition)
        def definition()
          if self.entities.parent.instances.include?(self)
            return self.entities.parent
          else
            Sketchup.active_model.definitions.each { |definition|
                return definition if definition.instances.include?(self)
            }
          end
          return nil # Should not happen.
        end
    end#if
end
		
class SmartDrop
   # Y value for the drop, convert to Length
   @@level = 0.m

   # validate the level entity (menu) - allow only Face entities
   def SmartDrop::validate_level_entity
      ss = Sketchup.active_model.selection
      return nil if ss.empty? 
      return true if (ss.first.kind_of? Sketchup::Face and ss.count==1)
   end

   # validate the drop entity (menu)  - allow only Groups and Components 
   def SmartDrop::validate_drop_entity
      ss = Sketchup.active_model.selection
      return nil if ss.empty?

      is_valid = nil
      ss.each do |e| 
         if (e.kind_of? Sketchup::Group) or (e.kind_of? Sketchup::ComponentInstance)   
            is_valid = true 
         else 
            is_valid = false
         end
      end

      return is_valid
   end
   
   # perform the drop
   def SmartDrop::perform_drop (entity, translation, xrotation, yrotation)
			Sketchup.active_model.active_entities.transform_entities(xrotation,entity)
			Sketchup.active_model.active_entities.transform_entities(yrotation,entity)
			Sketchup.active_model.active_entities.transform_entities(translation,entity)
			
      # invalidate the view to see the move action
      Sketchup.active_model.active_view.invalidate
   end

   # drop at intersection
   def SmartDrop::drop_intersection
			Sketchup.active_model.start_operation "Smart Drop"
      dir = [0,0,-1]
      ss = Sketchup.active_model.selection
      ss.each do |e|
				 e.hidden = true #hiding all groups/components to avoid self-interference of all dropped entities -will reveal all entities later
				 position = e.transformation.origin
				 #local bounding box for each entity
				 compdef=e.definition 
         groupbounds= Geom::BoundingBox.new 
         compdef.entities.each{|drawe| groupbounds.add(drawe.bounds)} #Calculate the bounding box of the entities in the component. 
				 localposition1 = groupbounds.corner 0
				 localposition2 = groupbounds.corner 1
				 localposition3 = groupbounds.corner 2
				 
				 originalpoint = [0,0,0]
				 vector1 = (localposition1 - originalpoint)
				 vector2 = (localposition2 - originalpoint)
				 vector3 = (localposition3 - originalpoint)
				 
				 #global position of three base points of the lower bounding box plane
				 position1 = position.offset(vector1)
				 position2 = position.offset(vector2)
				 position3 = position.offset(vector3)
				 
				 rt = Sketchup.active_model.raytest(position1,dir)
         dir_rt = [position1.x, position1.y, -1]
         if rt == nil
            Sketchup.active_model.active_entities.add_cline position1,dir_rt
         else
						##get bounding box corner ortogonal projection intersections
						rt1 = Sketchup.active_model.raytest(position1,dir)
						rt2 = Sketchup.active_model.raytest(position2,dir)
						rt3 = Sketchup.active_model.raytest(position3,dir)
						pt1 = rt[0]
						pt2 = rt2[0]
						pt3 = rt3[0]
						
						##determine deviation from ortogonal planes by comparing rt1 and rt2 and rt3
						##(xz plane) aligned surface deviation
						zdrop = pt1 - position1
						
						xorigvector = position2 - position1
						yorigvector = position3 - position1
						
						xvector = pt2 - pt1
						yvector = pt3 - pt1
						
						xangle = xvector.angle_between xorigvector
						yangle = yvector.angle_between yorigvector
						
						if pt2[2] > pt1[2] #surface is rising towards 2nd point, leave positive angle
							xangle = 0 - xangle
						end
						if pt3[2] < pt1[2] #surface is rising towards 3rd point, reverse the angle
							yangle = 0 - yangle
						end
						
						#prepare rotation in respective orientations and the translation 
						rotx = Geom::Transformation.rotation position1, yorigvector, xangle
						roty = Geom::Transformation.rotation position1, xorigvector, yangle
						translate = Geom::Transformation.translation zdrop
						
						#call executive command
						SmartDrop.perform_drop e, translate, rotx, roty
         end
      end
			ss.each do |e|
				e.hidden = false if e.hidden? #revealing all entities to see them
			end
		Sketchup.active_model.commit_operation
   end
end # Class SmartDrop

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

if( not file_loaded?("smartdrop.rb") )
   UI.add_context_menu_handler do |menu|
      menu.add_separator if (SmartDrop.validate_level_entity || SmartDrop.validate_drop_entity)
      menu.add_item("SmartDrop at intersection") { SmartDrop.drop_intersection } if SmartDrop.validate_drop_entity
   end
end

file_loaded("smartdrop.rb")
