#!BPY
"""
Name: 'Inset'
Blender: 248
Group: 'Mesh'
Tooltip: 'Inset a selection of faces'
"""

__author__ = ["macouno"]
__url__ = ("http://wiki.blender.org/index.php/Extensions:Py/Scripts/Manual/Mesh/Inset", "http://www.alienhelpdesk.com")
__version__ = "2"
__bpydoc__ = """\

Inset

Select the faces on your mesh that you want to inset and run the script.
The script doesn't nessecarily like kinks and poles... you may have to do a few fixes after running it.

Fixed exactness in version 2

"""

# ***** BEGIN GPL LICENSE BLOCK *****
#
# Script copyright (C) macouno 2007
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
# ***** END GPL LICENCE BLOCK *****
# --------------------------------------------------------------------------

from Blender import Window, Scene, Draw, Mathutils, Mesh
import BPyMesh
import math

inset = 0.05		# Default distance of the inset

## Get the angle between two vectors
def VtoVAngle(vec1, vec2):
	if (vec1.length == 0.0 or vec2.length == 0.0): return 0.0
	else: return Mathutils.AngleBetweenVecs(vec1, vec2)
	
## Rotate one vector (vec1) towards another (vec2)
## (deg = ammount of degrees to rotate)
def RotVtoV(vec1, vec2, deg, cross):
	if not cross:
		cross = Mathutils.CrossVecs(vec1,vec2)
	mat = Mathutils.RotationMatrix(deg, 3, 'r', cross)
	return (vec1 * mat)

## Find out if a vert is an outer vert (connected to a non selected vert by an edge)
def IsOuterVert(me, vIn):
	for f in me.faces:
		if not f.sel:
			vList = [v.index for v in f.verts]
			if vIn in vList:
				return True
	return False
	
## Find out if an edge is an outer edge (connected to a non selected face)
def IsOuterEdge(me, v1in, v2in):	
	found = False
	for f in me.faces:
		if not f.sel:
			vList = [v.index for v in f.verts]
			if v1in in vList:
				if v2in in vList:
					return True
	return False

# Create an inset
def MainScript(me):

	global inset

	# Make a list of all verts connected to non selected verts
	OuterVerts = []
	InnerVerts = []
	OuterEdges = []
	InnerEdges = []
	OutLocs = {}
	MadeVerts = {}
	
	## Find all the outer and inner verts
	for vIn in me.verts.selected():
		if IsOuterVert(me, vIn):
			OuterVerts.append(vIn)
			MadeVerts[vIn] = 0
		else:
			InnerVerts.append(vIn)
			
	## Find all the outer edges
	for eIn in me.edges.selected():
		v1in = me.edges[eIn].v1.index
		v2in = me.edges[eIn].v2.index
		if IsOuterEdge(me, v1in, v2in):
			OuterEdges.append(eIn)
		else:
			InnerEdges.append(eIn)
	
	DeleteFaces = [];
	
	print "found", len(OuterVerts), "outer verts";
	print "found", len(OuterEdges), "outer edges";
	
	for v1in in OuterVerts:
		
		v1co = Mathutils.Vector(me.verts[v1in].co)
		outVerts = []
		inVerts = []
		conLoc = Mathutils.Vector()
		checkOut = []
		OutRel = []
	
		# Find the outer edges this vert is connected to
		for eIn in OuterEdges:
			ev1 = me.edges[eIn].v1
			ev2 = me.edges[eIn].v2
			if ev1.index == v1in:
				outVerts.append(ev2.index)
			elif ev2.index == v1in:
				outVerts.append(ev1.index)
				
		print "-> FOUND", v1in, len(outVerts), len(OuterEdges)
			
		# No need to make points unless it's connected to two outer edges
		if len(outVerts) == 2:
				
			# Find the inner edges this vert is connected to
			for eIn in InnerEdges:
				ev1 = me.edges[eIn].v1
				ev2 = me.edges[eIn].v2
				if ev1.index == v1in:
					inVerts.append(ev2.index)				
				elif ev2.index == v1in:
					inVerts.append(ev1.index)
			
			cv1 = me.verts[outVerts[0]]
			cv2 = me.verts[outVerts[1]]
			
			vec1 = Mathutils.Vector(cv1.co) - v1co
			vec2 = Mathutils.Vector(cv2.co) - v1co
			
			vec1 = vec1.normalize()
			vec2 = vec2.normalize()
			
			ang = VtoVAngle(vec1, vec2)
			
			# If there's a 180 degree angle we need a custom cross product (we'll use the normal)
			if round(ang, 5) == 180.0:
				cross = me.verts[v1in].no
			else:
				cross = 0
			
			# If the vert is connected to inner verts we use their loc for the new edge
			if len(inVerts):
				
				#Find the average position of connected inner verts
				nLoc = Mathutils.Vector()
				for vIn in inVerts:
					nLoc += Mathutils.Vector(me.verts[vIn].co)
				nLoc /= len(inVerts)
				vNew = nLoc - v1co
				
				cross = Mathutils.CrossVecs(vec1,vNew)
				
			else:
				vNew = RotVtoV(vec1, vec2, (ang * 0.5), cross)
				
			vNew = vNew.normalize() 
			

			xVec = vec1 * inset
			yVec = RotVtoV(xVec, vec2, 90, cross)
			xVec += yVec
			
			# Find the proper location along vNew relative to the 90 degree rotated lengths
			inLoc = Mathutils.LineIntersect(yVec, xVec, Mathutils.Vector(), vNew)
			
			leng = inLoc[0].length
			
			vNew *= leng
			
			newLoc = v1co + vNew
			
			me.verts.extend(newLoc)
			nvin = len(me.verts) -1
			MadeVerts[v1in] = nvin
			
			print 'angle', ang, leng, newLoc
			
	VertKeys = MadeVerts.keys()
	
	## Add new faces where the outer edges were changed
	for fIn in me.faces.selected():
		makeNew = False
		newFace = []
		for v in me.faces[fIn].verts:
			vIn = v.index
			if vIn in VertKeys:
				makeNew = True
				newFace.append(MadeVerts[vIn])
			else:
				newFace.append(vIn)
		if makeNew:
			me.faces.extend(newFace)
			DeleteFaces.append(fIn)
			
	## Add new faces for the outer edges
	for eIn in OuterEdges:
		ev1 = me.edges[eIn].v1.index
		ev2 = me.edges[eIn].v2.index
	
		if ev1 in VertKeys and ev2 in VertKeys:
			me.faces.extend(ev1, MadeVerts[ev1], MadeVerts[ev2], ev2)
		
	# Remove the faces that are no longer needed
	if len(DeleteFaces):
		me.faces.delete(1, DeleteFaces)

#Make sure blender and the object are in the right state and start doing stuff
def MeshCheck():

	global inset
	
	inset = Draw.PupFloatInput('inset', inset, 0.001, 100, 0.1, 3)

	if inset:
	
		print '->'

		Window.WaitCursor(1)

		emode = int(Window.EditMode())
		if emode: Window.EditMode(0)

		scn = Scene.GetCurrent()
		ob = scn.getActiveObject()
		
		if not ob or ob.getType() != 'Mesh':
			Draw.PupMenu('Error, no active mesh object, aborting.')
			return
			
		me = ob.getData(mesh=1)
		del ob

		if not len(me.faces.selected()):
			Draw.PupMenu('Error, no faces selected, aborting.')
			return
			
		if not len(me.faces.selected()) < len(me.faces):
			Draw.PupMenu('Error, all faces are selected, aborting.')
			return
		
		BPyMesh.meshCalcNormals(me)
		MainScript(me)
		
		me.update()
		scn.update()
		del scn, me

		Window.Redraw(Window.Types.VIEW3D)
		
		if emode: Window.EditMode(1)
		
		Window.WaitCursor(0)

		print '<-'
	
## Initialise the script
MeshCheck()	