require 'sketchup.rb'
#SketchyBevel 
#Bevel all selected faces.
#Adds a context menu item called Bevel
#
#
#Copyright 2008 Chris Phillips


module SketchyBevel
    if($bevel_menu_loaded==nil)
        $bevel_menu_loaded=true
        
        if(Sketchup.active_model.options["UnitsOptions"]["LengthUnit"]<2)
            $bevelSettings = [1.0.inch, "true"]
        else
            $bevelSettings = [5.0.cm, "true"]
        end           
        UI.add_context_menu_handler { |menu|
            selection=Sketchup.active_model.selection
            
            if(!selection.empty?)
                menu.add_item("Bevel"){            
                    prompts = ["Amount","Cap Holes"]#,"Interations"]
                    results = inputbox(prompts, $bevelSettings,["","true|false"], "Bevel selection")
                    if (results)
                        $bevelSettings=results
                        Sketchup.active_model.selection.bevel(results[0],results[1])
                    end
                }
                menu.add_item("Cap Holes"){
                    Sketchup.active_model.start_operation("Cap Holes")                
                    Sketchup.active_model.selection.cap_holes()
                    Sketchup.active_model.commit_operation()
                }
            end
        }
    end
    class Sketchup::Selection
        
        def find_adjcent_coplaner_edges(edge,plane)
            edges=[]
            edge.vertices.each{|v|
                v.edges.each{|e|
                    next if(e==edge)
                    if(!e.get_attribute("SPCAP","used",false)&&
                            e.start.position.on_plane?(plane)&& e.end.position.on_plane?(plane))
                        edges.push(e)
                        e.set_attribute("SPCAP","used",true)
                        edges.push(find_adjcent_coplaner_edges(e,plane))
                    end
                }
            }
            return edges.flatten
                    
        end
        
        def cap_holes()
            self.faces.each{|f|
                    f.edges.each{|e|e.delete_attribute("SPCAP","used")}
                }
            
            newfaces=[]
            while(true)
                caps=[]
                self.faces.each{|f|
                    f.edges.each{|e|
                        if(e.faces.length<2)
                            e.vertices.each{|v|
                                v.edges.each{|ve|
                                    if(e!=ve && ve.faces.length<2 && 
                                            !e.get_attribute("SPCAP","used",false) && !ve.get_attribute("SPCAP","used",false) )
                                            
                                            
                                        #make face out of e and ve
                                        #e.set_attribute("SPCAP","used",true)
                                        #ve.set_attribute("SPCAP","used",true)
                                        pts=[v.position,e.other_vertex(v).position,ve.other_vertex(v).position]
                                        plane=Geom.fit_plane_to_points(pts)
                                        ce=find_adjcent_coplaner_edges(ve,plane)
                                        #ce.push(ve)
                                        #puts ce.inspect
                                        caps.push(ce)
                                    end
                                }
                            }
                        end
                    }
                }
                caps.each{|f|
                    if(f.length==2)
                        nf= [f[0].start.position.to_a,f[0].end.position.to_a,f[1].start.position.to_a,f[1].end.position.to_a].uniq
                        #puts "tri"
                        #puts nf.inspect
                        newfaces.push(Sketchup.active_model.active_entities.add_face(nf))
                    else
                        #puts "quad"
                        #puts f.inspect
#puts f                        
#cf=f.select{|tf|!tf.deleted?}
                        newfaces.push(Sketchup.active_model.active_entities.add_face(f))
                    end
                }
                #self.clear()
                #self.add(caps)
                #puts newfaces
                #self.add(newfaces) if(!newfaces.empty?)
                self.faces.each{|f|
                    f.edges.each{|e|e.delete_attribute("SPCAP","used")}
                }
                puts caps.length
                break# if(caps.length==0)#found no caps so exit loop.
                    
            end                
            return newfaces
        end
        def old_cap_holes()
            newfaces=[]
            while(true)
                caps=[]
                self.faces.each{|f|
                    f.edges.each{|e|
                        if(e.faces.length<2)
                            e.vertices.each{|v|
                                v.edges.each{|ve|
                                    if(e!=ve && ve.faces.length<2 && 
                                            !e.get_attribute("SPCAP","used",false) && !ve.get_attribute("SPCAP","used",false) )
                                        #make face out of e and ve
                                        e.set_attribute("SPCAP","used",true)
                                        ve.set_attribute("SPCAP","used",true)
                                        caps.push([v.position,e.other_vertex(v).position,ve.other_vertex(v).position])
                                    end
                                }
                            }
                        end
                    }
                }
                caps.each{|f|newfaces.push(Sketchup.active_model.active_entities.add_face(f))}
                self.add(newfaces)
                self.faces.each{|f|
                    f.edges.each{|e|e.delete_attribute("SPCAP","used")}
                }
                puts caps.length
                break if(caps.length==0)#found no caps so exit loop.
                    
            end                
            return newfaces
        end
        
        
        def bevel(amount,bCapHoles)
            Sketchup.active_model.start_operation("Bevel")
            
            #build a nested array of faces[edges[verts]]
            allFaces=self.faces.collect{|f|
                verts=f.outer_loop.vertices
                edges=[]
                i=0
                while i<verts.length
                    edges.push([verts[i].position,verts[(i+1)%verts.length].position])
                    i=i+1
                end
                edges
            }
            #make a simple array of all the points        
            allPoints=allFaces.flatten
            #find any duplicate points. These points will make cap faces later.
            dupePoints=allPoints.collect{|p|allPoints.select{|p2|p==p2}}
            
            #simple array of all edges
            allEdges=[]
            allFaces.each{|f|
                f.each{|edge|allEdges.push(edge)}
            }
            
            #find overlapping edges. Each will be a bevel face
            dupeEdges=[]
            allEdges.each_with_index{|e,i|
                allEdges.each_with_index{|e2,i2| 
                    if(i!=i2&& (e[0]==e2[0]&&e[1]==e2[1])||(e[1]==e2[0]&&e[0]==e2[1]))
                        dupeEdges.push([e,e2])
                    end
                }
            }
            
            shrunkFaces=[]
            #Create a shrunken version of each face
            allFaces.each{|f|
                SketchyBevel::shrink_face(f,amount)
                pts=f.collect{|edge|edge[0]}
                shrunkFaces.push(Sketchup.active_model.active_entities.add_face(pts))
            }
            #Create the bevel. A 4 sided polygon made from each formerly overlapping edge
            newFaces=[]
            dupeEdges.each{|edge|
                newFaces.push(Sketchup.active_model.active_entities.add_face(edge.flatten))
            }
            
bCapHoles="false"
            #~ caps=[]
            #~ capEdges=newFaces.each{|f|
                #~ f.edges.each{|e|
                    #~ if(e.faces.length<2)
                        #~ e.vertices.each{|v|
                            #~ v.edges.each{|ve|
                                #~ if(!e.get_attribute("SPBEVEL","alreadybeveled",false) && 
                                    #~ !ve.get_attribute("SPBEVEL","alreadybeveled",false) && e!=ve && ve.faces.length<2)
                                    #~ #make face out of e and ve
                                    #~ e.set_attribute("SPBEVEL","alreadybeveled",true)
                                    #~ ve.set_attribute("SPBEVEL","alreadybeveled",true)
                                    #~ caps.push([v.position,e.other_vertex(v).position,ve.other_vertex(v).position])
                                #~ end
                            #~ }
                        #~ }
                    #~ end
                    #~ }
                #~ }
                
            #~ caps.each{|f|Sketchup.active_model.entities.add_face(f)}

            #capEdges=newFaces.collect{|f|
            #    f.edges.select{|e|e.faces.length<2}
            #    }
            #puts "caps"
            #puts capEdges.inspect
            
            #~ #cap the corners by drawing a face around each formerly duplicated point.
            dupePoints.each{|pts|
                #remove any duplicated points.
                tpts=pts.collect{|p|p.to_a}.uniq
                case tpts.length
                when 3
                    Sketchup.active_model.active_entities.add_face(tpts)
                when 4
                    0.upto(tpts.length-2){|i|
                        tri=[tpts[i],tpts[(i+1)%tpts.length],tpts[(i+2)%tpts.length]]
                        Sketchup.active_model.active_entities.add_face(tri)    
                    }   
                
                    #Sketchup.active_model.entities.add_face(tpts[0],tpts[1],tpts[2])
                    #Sketchup.active_model.entities.add_face(tpts[1],tpts[2],tpts[3])
                    #ca=(tpts[2]-tpts[1]).cross(tpts[3]-tpts[2])
                    #if((tpts[3]-tpts[2]).cross(tpts[0]-tpts[3]).dot(ca)<0.0)
                        #Sketchup.active_model.entities.add_face(tpts[2],tpts[3],tpts[0])
                    #else
                        #Sketchup.active_model.entities.add_face(tpts[1],tpts[3],tpts[0])
                    #end
                    #Sketchup.active_model.entities.add_face(tpts.slice(0,3))
                end

            } if(bCapHoles=="true")


            #remove all the old geometry.
            Sketchup.active_model.selection.each{|e|e.erase!}
            
            Sketchup.active_model.selection.add(shrunkFaces)            
            Sketchup.active_model.selection.add(newFaces)
            Sketchup.active_model.selection.add(cap_holes())

            Sketchup.active_model.commit_operation()
        end
        def faces()
            self.select{|s|s.class==Sketchup::Face}    
        end
    end
    
    
    def self.shrink_face(face,amount)
        
        #TODO.face is really edges[].
        
        
        normal=(face.first[1]-face.first[0]).cross(face.last[1]-face.last[0]).normalize
        if(normal.length==0)
            plane=Geom.fit_plane_to_points([face[0][0],face[0][1],face[1][0],face[1][1],face[2][0],face[2][1]])
            puts normal
            normal=Geom::Vector3d.new(plane[0],plane[1],plane[2])
            puts normal
        end
        #normal=face.normal
        #Sketchup.active_model.entities.add_line(face.first[0],face.first[0]+normal)
        
        #find the direction to move each vert in face
        shrinkVectors=[]
        lastEdge=face.last #use last vert in face is connected to the first vert in face.
        face.each{|edge|
            #create 2 vectors from each edge
            va=lastEdge[0]-lastEdge[1]
            vb=edge[1]-edge[0]
            lastEdge=edge #use this next loop.
            #find angle between the two vectors.        
            if(va.cross(vb).dot(normal)<0) #check to see if angle >180
                angle=(360.degrees-va.angle_between(vb)) #angle is >180
            else
                angle=va.angle_between(vb)
            end
            #rotate one vector 1/2 the angle between the two edges
            sv=va.transform(Geom::Transformation.new([0,0,0],normal,angle/2))
            #set the distance to move based on the amount of bevel.
            sv.length=amount/Math.sin(angle/2)
            #save.
            shrinkVectors.push(sv)
        }
        
        edge=face[0]
        before=(edge[0]-edge[1]).length
        after=(edge[0].transform(shrinkVectors[0])-edge[1].transform(shrinkVectors[1])).length
        
        
        bFlipped=false
        #~ if(edge[0]!=edge[1]&& (edge[0].transform(shrinkVectors[0])-edge[1].transform(shrinkVectors[1])).length>before)
            #~ bFlipped=true
        #~ end
        
        #now move each edge by the amount calculated. This actually shrinks the face.
        face.each_index{|index|
            edge=face[index]
            if(!bFlipped)
                edge[0].transform!(shrinkVectors[index])
                edge[1].transform!(shrinkVectors[(index+1)%face.length])
            else
                edge[0].transform!(shrinkVectors[index].reverse)
                edge[1].transform!(shrinkVectors[(index+1)%face.length].reverse)
            end
        }
    end
end

















