# Name :          Mirror 3.0 ###

# Description :   Mirrors Selection at Point, Line or Plane

# Author :        Originally by Frank Wiesner
#				  BUT NOTE from v2.6 it's been TIG tweaked and rewritten...###

# Usage :         1. Make Selection (Any Entites allowed)###
#                 2. Select 'Mirror Selection' from Plugins Menu 
#					 or from Context Menu or your ShortCut Key ###
#                 3. If here's no Selection then dialog informs ###
#                 4. Pick a Point and RETURN to Mirror at the Point
#                 5. Pick a Second Point and RETURN to Mirror at the Line
#                 6. Pick a Third Point to Mirror at the Plane
#                 7. Final Option is Erase Original ? ###
#					 If Yes then Highlighting of Selection passes to Copies
#					 (Note: Erase is undo-able separately from the Mirroring) ###

# Type :      	Script

# History: 
#       3.1  21 Feb  2007 ### TIG make_unique tries to avoid rare bugsplats.
#				3.0  05 Aug  2006 ### TIG Ending rewritten to avoid rare bugsplats.
#				2.9  03 Aug  2006 ### TIG Start:commit loops to avoid rare bugsplats.
#				2.8  26 July 2006 ### TIG re-tweaked - Rewritten to be MUCH simpler.
#										Mirroring of ANY Entity Selection works properly.
#				2.7  23 July 2006 ### TIG tweaked - Rewritten generally.
#										Now processes Multiple selections of Groups and/or 
#										Components, but warns if any 'loose' (UnGrouped) 
#										Faces in Selection as they might lose any RePostioned 
#										Texture data, interfere etc.  
#				2.6  22 July 2006 ### TIG tweaked - Context menu added IF ONE group/compo.  
#                                       Warns about selection IF NOT ONE group/compo.  
#                                       Materials now preserved in mirrored copy BUT you will 
#									    need to group selection to preserve material positioning 
#										- although this is define-able it is not find-able from 
#										the orignal face info !  
#                                       Final option to Erase of Original Group/Compo.
#                 2.5  (8.Jul.2oo5)  - bugfix: faces with holes reduce now to a single face
#                                    - major code rewrite (dropped @clondedObject hash, heavy 
#                                      use of groups during construction,...
#                                    - more redundant constructions, but seems to be more robust
#                                    - WARNING: POLYGONS DO NOT WORK VERY WELL
#                 2.4  (5.Jul.2oo5)  - all unnecessary edges created by the add_faces_from_mesh() 
#                                      method are erased! faces with holes mirror now perfectly.
#                 2.3  (4.Jul.2oo5)  - fixed "mirror at plane" error
#                 2.2  (2.Jul.2oo5)  - works in group editting mode
#                                    - prevents picking of identical points
#                 2.1  (1.Jul.2oo5)  - fixed "undo group" bug
#                                    - code cleanup
#                 2.0  (30.Jun.2oo5) - new tool-like interface
#                 1.4  (29.Jun.2oo5) - arc bug fixed
#                                    - code simplified (clone_selection() removed)
#                 1.3  (24.Jun.2oo5) - can handle faces with holes in it (although more edges 
#                                      are drawn than nessesary)
#                                    - correct orientation of mirrored faces 
#                                    - supports arcs (at least works for circles), curves and groups
#                 1.2  (20.Jun.2oo5) - axis and origin selection algorithm does not rely on tool tips any more.
#                 1.1  (19.Jun.2oo5) - fix coplanar point bug
#                                    - better tooltips
#                                    - supports mirror at lines (edges, axis, construction lines)
#                                    - mirror-at-plane code simplified
#                 1.o  (10.Jun.2oo5) - first version

require 'sketchup.rb'
class MirrorTool
    def initialize
        @MIRROR_AT_POINT = 1
        @MIRROR_AT_LINE = 2
        @MIRROR_AT_PLANE = 3
        @numPrecision = 0.00001
    end
    def reset
        @pts = []
        @state = 0
        @transformationType = nil
        @trans = nil
        @point = nil
        @line = nil
        @linev = nil
        @plane = nil
        @planeNormal = nil
        @pointOnPlane = nil
        @curvesDone = Hash.new()
        @cloneGroup = nil
        @ip = Sketchup::InputPoint.new
        @ip1 = Sketchup::InputPoint.new
        @ip2 = Sketchup::InputPoint.new
        @ip3 = Sketchup::InputPoint.new
        @ip = Sketchup::InputPoint.new
        Sketchup::set_status_text "Mirror:  Pick First Point"###
        @drawn = false
    end
    def activate
        ###Sketchup.send_action("showRubyPanel:")
        self.reset
        ss=Sketchup.active_model.selection
        if ss.empty?
            Sketchup::set_status_text "Mirror: NO Selection !"###
    		UI.messagebox("Select Something BEFORE Using the Mirror Tool.")###v2.8
            Sketchup.active_model.select_tool nil
        end
    end
    def deactivate(view)
        view.invalidate if @drawn
        @ip1 = nil
        @ip2 = nil
        @ip3 = nil
    end
    def onMouseMove(flags, x, y, view)
        case @state
        when 0 # getting the first end point
            @ip.pick view, x, y
            if( @ip.valid? && @ip != @ip1 )
                @ip1.copy! @ip
            end
            view.invalidate
            view.tooltip = @ip.tooltip if @ip.valid?
        when 1 # getting the second end point
            @ip.pick view, x, y, @ip1
            if( @ip.valid? && @ip != @ip2 )
                @ip2.copy! @ip
                @pts[1] = @ip2.position
            end
            view.invalidate
            view.tooltip = @ip.tooltip if @ip.valid?
        when 2 # getting the third end point
            @ip.pick view, x, y, @ip2
            if( @ip.valid? && @ip != @ip3 )
                @ip3.copy! @ip
                @pts[1] = @ip3.position
            end
            view.invalidate
            view.tooltip = @ip.tooltip if @ip.valid?
        end
    end
    def onLButtonDown(flags, x, y, view)
        @ip.pick view, x, y
        if( @ip.valid? )
            case @state
            when 0
                if( @ip.valid? )
                    @pts[0] = @ip.position
                    Sketchup::set_status_text "Mirror:  Hit RETURN to Mirror at Point or Pick Point 2"
                    @state = 1
                end
            when 1
                if( @ip.valid? && @ip != @ip1 )
                    @pts[1] = @ip.position
                    @state = 2
                    Sketchup::set_status_text "Mirror:  Hit RETURN to Mirror at Line or Pick Point 3"
                end
            when 2
                if( @ip.valid? && @ip != @ip1 && @ip != @ip2 )
                    @pts[2] = @ip.position
                    @state = 3
                    mirror()
                end
            end
        end
    end
    def onCancel(flag, view)
        view.invalidate if @drawn
        reset
    end
    def draw(view)
        if( @ip.valid? && @ip.display? )
            @ip.draw(view)
            @drawn = true
        end
        if( @state == 1 )
            view.set_color_from_line(@ip1, @ip)
            view.draw(GL_LINE_STRIP, @ip1.position, @ip.position)
            @drawn = true
        elsif( @state == 2 )
            view.drawing_color = "gray"
            view.draw(GL_LINE_STRIP, @ip1.position, @ip2.position)
            view.set_color_from_line(@ip2, @ip)
            view.draw(GL_LINE_STRIP, @ip2.position, @ip.position)
            @drawn = true
        end
    end
    def onKeyDown(key, repeat, flags, view)
        if( key == CONSTRAIN_MODIFIER_KEY && repeat == 1 )
            @shift_down_time = Time.now
            if( view.inference_locked? )
                view.lock_inference
            elsif( @ip.valid? )
                view.lock_inference @ip
            end
        end
    end
    def onKeyUp(key, repeat, flags, view)
        if( key == CONSTRAIN_MODIFIER_KEY && view.inference_locked? && (Time.now - @shift_down_time) > 0.5 )
            view.lock_inference
        end
        if( key == 13)
            mirror()
            self.reset
        end
    end
    def mirror()
        case @state
        when 1
            @transformationType = @MIRROR_AT_POINT
            @point = @ip1.position.clone
            @trans = Geom::Transformation.scaling(@point, -1.0)
            Sketchup.active_model.start_operation "Mirror at Point"
        when 2
            @transformationType = @MIRROR_AT_LINE
            @line = [@ip1.position, @ip2.position] 
            @linev = Geom::Vector3d.new(@ip2.position.x-@ip1.position.x, @ip2.position.y-@ip1.position.y, @ip2.position.z-@ip1.position.z)
            Sketchup.active_model.start_operation "Mirror at Line"
        when 3
            @transformationType = @MIRROR_AT_PLANE
            @plane = Geom.fit_plane_to_points(@ip1.position, @ip2.position, @ip3.position) 
            @planeNormal = Geom::Vector3d.new(@plane[0], @plane[1], @plane[2])
            @planeNormal.normalize!
            @pointOnPlane = @ip1.position
            Sketchup.active_model.start_operation "Mirror at Plane"
        end
        ###
        ss=Sketchup.active_model.selection###
    	### fix group glitch v3.1
      def group_miner(ents)
        ents.each{|e|
        if e.typename=="Group"
          e.make_unique
          group_miner(e.entities)
        end#if
        }#end each
      end#def
      group_miner(ss)
      ###
      sents=[]
    	ss.each{|e|sents.push(e)}
    	  copy_group=Sketchup.active_model.active_entities.add_group(sents)
      group_miner(copy_group.entities)###v3.1
        new_group=copy_group.copy
      group_miner(new_group.entities)###v3.1
        nents=[]
        new_group.entities.each{|e|nents.push(e)}
        new_center = new_group.bounds.center.clone
        mirror_point(new_center)
        t=nil
        t = Geom::Transformation.scaling(new_center,-1,-1,-1)if @state==1
        t = Geom::Transformation.rotation(new_center,@linev,Math::PI)if @state==2
        tt=nil
        if @state==3
           xxx=@planeNormal.normalize.to_a[0].abs*-1
           yyy=@planeNormal.normalize.to_a[1].abs*-1
           zzz=@planeNormal.normalize.to_a[2].abs*-1
           xxx=1 if xxx>0
           yyy=1 if yyy>0
           zzz=1 if zzz>0
           xxx=-1 if xxx<=0 
           yyy=-1 if yyy<=0
           zzz=-1 if zzz<=0
           t = Geom::Transformation.scaling(new_center,xxx,yyy,zzz)
           tt= Geom::Transformation.rotation(new_center,@planeNormal,Math::PI)
        end#if
        new_group.transform!(t)if t###mirror
        new_group.transform!(tt)if tt###
        transVec = Geom::Vector3d.new(new_center.x-new_group.bounds.center.x, new_center.y-new_group.bounds.center.y, new_center.z-new_group.bounds.center.z) 
        t = Geom::Transformation.translation(transVec)
        new_group.transform!(t)###then move
        ### ending dialog...
    	Sketchup::set_status_text "Mirror: Erase Original Selection ?"###
    	if UI.messagebox("Erase Original Selection ? ",MB_YESNO,"")==6
    	   ### 6=YES 7=NO
    	   Sketchup.active_model.commit_operation###v2.9
      	  Sketchup.active_model.start_operation "Mirror: Erase Original Selection"
    	    Sketchup.active_model.selection.clear
          group_miner(copy_group.entities)###v3.1
          group_miner(new_group.entities)###v3.1
      	  copy_group.erase! if copy_group.valid?###v3.1
    	    new_group.explode if new_group.valid?###v3.1
      	  nents.each{|e|Sketchup.active_model.selection.add(e)if e.valid?}###v3.0
    	   Sketchup.active_model.commit_operation
      	else
           Sketchup.active_model.selection.clear
            group_miner(copy_group.entities)###v3.1
            group_miner(new_group.entities)###v3.1
            new_copy=new_group.copy;group_miner(new_copy.entities)###v3.1
            new_group.erase! if new_group.valid?###v3.1
            new_copy.explode if new_copy.valid?###v3.1
            copy_group.explode if copy_group.valid?###v3.1
            sents.each{|e|Sketchup.active_model.selection.add(e)if e.valid?}###v3.0
           Sketchup.active_model.commit_operation###v2.9
    	end#if
    	###
        self.reset
        Sketchup.send_action "selectSelectionTool:"
    end#def
    def mirror_point(new_p)
        if (@transformationType == @MIRROR_AT_PLANE)
            if (!new_p.on_plane?(@plane))
                mirrorp = new_p.project_to_plane(@plane)
                @trans = Geom::Transformation.scaling(mirrorp, -1.0)
                new_p.transform!(@trans)
            end
        elsif (@transformationType == @MIRROR_AT_LINE)
            if (!new_p.on_line?(@line))
                mirrorp = new_p.project_to_line(@line)
                @trans = Geom::Transformation.scaling(mirrorp, -1.0)
                new_p.transform!(@trans)
            end
        elsif (@transformationType == @MIRROR_AT_POINT)
            if (!(new_p == @point))
                new_p.transform!(@trans)
            end
        end
    end#def
    
end # class MirrorTool
### menus ##############################################################
if( not file_loaded?("mirror.rb") )
###if( not file_loaded?("59-mirror.rb") )###for TIG menus only
   UI.menu("Plugins").add_item("Mirror Selection") { Sketchup.active_model.select_tool MirrorTool.new }
   ###$submenu5.add_item("Mirror [Selection]") { Sketchup.active_model.select_tool MirrorTool.new }###for TIGmenus only
   ### Context menu
   UI.add_context_menu_handler do | menu |### v2.6 TIG
      if Sketchup.active_model.selection
         menu.add_separator
         menu.add_item("Mirror Selection") { Sketchup.active_model.select_tool MirrorTool.new }
      end#if
   end#do menu###
end
###file_loaded("59-mirror.rb")###for TIG menus only
file_loaded("mirror.rb")

