#!BPY

"""
Name: 'Measure Mesh'
Blender: 248
Group: 'Misc'
Tooltip: 'Get some measurements of your mesh.'
"""

__author__ = "macouno"
__url__ = ("http://www.alienhelpdesk.com")
__version__ = "3 03/04/06"

__bpydoc__ = """\

Usage:

Select the object you want to know a measurement of.
Run the script.

The result will be printed in the console window as well as the gui.

"""
#############################################################
# Code history                                              #
#############################################################
#	version 1 released 20-03-2006

# --------------------------------------------------------------------------
# ***** BEGIN GPL LICENSE BLOCK *****
#
# Copyright (C) 2006: macouno, http://www.macouno.com
#
# 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 *****
# --------------------------------------------------------------------------

import Blender
from Blender import *
import math

####################################################
# GLOBALS                                          #
####################################################

VERSION ='3'

DATAHASH = {
	'SIZE': Draw.Create(0.0),
	'MODE': Draw.Create(1),
	'REL': Draw.Create(1),
	'TYPE': Draw.Create(1),
	'MODETEXT': Draw.Create(1),
	'RELTEXT': Draw.Create(1),
	'TYPETEXT': Draw.Create(1),
	'OBNAME': Draw.Create("a"),
	'ERROR': Draw.Create(0)
	}
	
## Make some lists for text display only.

TypeList = ["", "total", "average", "largest", "smallest"];

ModeList = ["", "face surface area", "edge length", "distance between verts", "distance from centre", "uv surface area"];

RelList = ["", "global", "local"];

ErrorText = ["", "You can only measure a mesh object!", "This mesh has no assigned uv layout!"];

####################################################
# BASIC FUNCTIONS                                  #
####################################################

####################################################
# TRANSFORMATIONS TO GET GLOBAL COORDS             #
####################################################

# Apply a matrix to a vert and return a vector.
def apply_transform(verts, matrix):
	x, y, z = verts
	xloc, yloc, zloc = matrix[3][0], matrix[3][1], matrix[3][2]
	return Mathutils.Vector(
	x*matrix[0][0] + y*matrix[1][0] + z*matrix[2][0] + xloc,
	x*matrix[0][1] + y*matrix[1][1] + z*matrix[2][1] + yloc,
	x*matrix[0][2] + y*matrix[1][2] + z*matrix[2][2] + zloc)

# Apply a matrix to a vert and return a vector.
def normal_transform(verts, matrix):
	matrix = matrix.rotationPart()
	x, y, z = verts
	return Mathutils.Vector(
	x*matrix[0][0] + y*matrix[1][0] + z*matrix[2][0],
	x*matrix[0][1] + y*matrix[1][1] + z*matrix[2][1],
	x*matrix[0][2] + y*matrix[1][2] + z*matrix[2][2])


####################################################
# FACE AREA FUNCTION                               #
####################################################

## Get the area of a face
def faceArea(face):
	tarObj = Object.Get(DATAHASH['OBNAME'].val)
	ObMatrix = Mathutils.Matrix(tarObj.getMatrix('worldspace'));
	## Try to get the area of a quad
	if len(face.v) == 4:
		v1, v2, v3, v4 = face.v
		## Check for relativity
		if DATAHASH['REL'].val == 1:
			v1, v2, v3, v4 = apply_transform(v1.co, ObMatrix), apply_transform(v2.co, ObMatrix), apply_transform(v3.co, ObMatrix), apply_transform(v4.co, ObMatrix) 
		else:
			v1, v2, v3, v4 = v1.co, v2.co, v3.co, v4.co
		e1 = v1-v2
		e2 = v3-v2
		e3 = v3-v4
		e4 = v4-v1
		cp = Blender.Mathutils.CrossVecs(e1,e2) 
		area = cp.length/2 
		cp2 = Blender.Mathutils.CrossVecs(e3,e4) 
		area2 = cp2.length/2
		area = area + area2
	## If no try to get the area of the tri
	elif len(face.v) == 3:
		v1, v2, v3 = face.v
		## Check for relativity
		if DATAHASH['REL'].val == 1:
			v1, v2, v3 = apply_transform(v1.co, ObMatrix), apply_transform(v2.co, ObMatrix), apply_transform(v3.co, ObMatrix)
		else:
			v1, v2, v3 = v1.co, v2.co, v3.co
		e1 = v1-v2 
		e2 = v3-v2 
		cp = Mathutils.CrossVecs(e1,e2) 
		area = cp.length/2
	## In case of a floating loose vert for instance
	else:
		area = 0.0;
	return area

####################################################
# EDGE LENGTH FUNCTION                             #
####################################################

## Function for getting the edge length of a face.
def edgeLength(v1, v2):

	tarObj = Object.Get(DATAHASH['OBNAME'].val)
	ObMatrix = Mathutils.Matrix(tarObj.getMatrix('worldspace'));
	## Try to get the longest edge of a quad
	
	if DATAHASH['MODE'].val == 4:
		## Check for relativity
		if DATAHASH['REL'].val == 1:
			v1co = apply_transform(v1.co, ObMatrix);
		else:
			v1co = v1.co;
			
		v2co = [0.0,0.0,0.0];
	
	else:
	
		if DATAHASH['REL'].val == 1:
			v1co, v2co, = apply_transform(v1.co, ObMatrix), apply_transform(v2.co, ObMatrix) 
		else:
			v1co, v2co = v1.co, v2.co
		
	length = Mathutils.Vector(v1co[0]-v2co[0], v1co[1]-v2co[1], v1co[2]-v2co[2]).length 
	
	return length

####################################################
# GET UV AREA                                      #
####################################################

## Function for getting the area of a face's uv coords;
def uvArea(face):

	if NMesh.GetRaw(DATAHASH['OBNAME'].val).hasFaceUV() == 0:
		area = 0.0;
	else:	
		## Try to get the area of a quad
		if len(face.v) == 4:
			v1 = Mathutils.Vector(face.uv[0][0], face.uv[0][1], 0.0)
			v2 = Mathutils.Vector(face.uv[1][0], face.uv[1][1], 0.0)
			v3 = Mathutils.Vector(face.uv[2][0], face.uv[2][1], 0.0)
			v4 = Mathutils.Vector(face.uv[3][0], face.uv[3][1], 0.0)
			e1 = v1-v2 
			e2 = v3-v2
			e3 = v3-v4
			e4 = v4-v1
			cp = Blender.Mathutils.CrossVecs(e1,e2) 
			area = cp.length/2 
			cp2 = Blender.Mathutils.CrossVecs(e3,e4) 
			area2 = cp2.length/2
			area = area + area2
		## If no try get the area of the tri
		elif len(face.v) == 3:
			v1 = Mathutils.Vector(face.uv[0][0], face.uv[0][1], 0.0)
			v2 = Mathutils.Vector(face.uv[1][0], face.uv[1][1], 0.0)
			v3 = Mathutils.Vector(face.uv[2][0], face.uv[2][1], 0.0)
			e1 = v1-v2 
			e2 = v3-v2 
			cp = Mathutils.CrossVecs(e1,e2) 
			area = cp.length/2
		else:
			area = 0.0;
		print area;
	return area
	
####################################################
# GET SOME SIZES                                   #
####################################################

def getSizes():

	## Make sure we're dealing with a mesh
	if Object.GetSelected()[0].getType() == "Mesh":

		DATAHASH['OBNAME'].val = Object.GetSelected()[0].getName();

		me = NMesh.GetRaw(DATAHASH['OBNAME'].val);

		TotalArea = 0.0;

		## Get distance beween verts of the object
		if DATAHASH['MODE'].val == 5:
		
			if NMesh.GetRaw(DATAHASH['OBNAME'].val).hasFaceUV() != 0:
		
				for a in range(len(me.faces)):

					Area = uvArea(me.faces[a]);

					## getting total and average areas
					if DATAHASH['TYPE'].val == 1 or DATAHASH['TYPE'].val == 2:

						TotalArea = TotalArea + Area;

					## getting the biggest area
					elif DATAHASH['TYPE'].val == 3:

						if Area > TotalArea:		
							TotalArea = Area;

					## getting the smallest area
					elif DATAHASH['TYPE'].val == 4:

						if Area < TotalArea or a == 0:		
							TotalArea = Area;
							
				DATAHASH['ERROR'].val = 0;
					
			else:
				TotalArea = 0.0;
				DATAHASH['ERROR'].val = 2;
				
					
		## Get distance beween verts of the object
		elif DATAHASH['MODE'].val == 4:

			for a in range(len(me.verts)):
						
				Area = edgeLength(me.verts[a], 0);

				## getting total and average areas
				if DATAHASH['TYPE'].val == 1 or DATAHASH['TYPE'].val == 2:

					TotalArea = TotalArea + Area;

				## getting the biggest area
				elif DATAHASH['TYPE'].val == 3:

					if Area > TotalArea:		
						TotalArea = Area;

				## getting the smallest area
				elif DATAHASH['TYPE'].val == 4:

					if Area < TotalArea or a == 0:
						TotalArea = Area;
						
					print Area;
					
			DATAHASH['ERROR'].val = 0;
		
		## Get distance beween verts of the object
		elif DATAHASH['MODE'].val == 3:

			for a in range(len(me.verts)):
			
				for b in range(len(me.verts)):
				
					if a != b:
						
						Area = edgeLength(me.verts[a], me.verts[b]);

						## getting total and average areas
						if DATAHASH['TYPE'].val == 1 or DATAHASH['TYPE'].val == 2:

							TotalArea = TotalArea + Area;

						## getting the biggest area
						elif DATAHASH['TYPE'].val == 3:

							if Area > TotalArea:		
								TotalArea = Area;

						## getting the smallest area
						elif DATAHASH['TYPE'].val == 4:
						
							if Area < TotalArea or a == 0 and b == 1:
								TotalArea = Area;
								
			DATAHASH['ERROR'].val = 0;
		
		
		## Get Edge lengths of the object
		elif DATAHASH['MODE'].val == 2:

			for a in range(len(me.edges)):
						
				Area = edgeLength(me.edges[a].v1, me.edges[a].v2);
				
				## getting total and average areas
				if DATAHASH['TYPE'].val == 1 or DATAHASH['TYPE'].val == 2:

					TotalArea = TotalArea + Area;

				## getting the biggest area
				elif DATAHASH['TYPE'].val == 3:

					if Area > TotalArea:		
						TotalArea = Area;

				## getting the smallest area
				elif DATAHASH['TYPE'].val == 4:

					if Area < TotalArea or a == 0:		
						TotalArea = Area;
						
			DATAHASH['ERROR'].val = 0;
				
		## Get the areas for the object
		else:
			for a in range(len(me.faces)):

				Area = faceArea(me.faces[a]);
				
				## getting total and average areas
				if DATAHASH['TYPE'].val == 1 or DATAHASH['TYPE'].val == 2:

					TotalArea = TotalArea + Area;

				## getting the biggest area
				elif DATAHASH['TYPE'].val == 3:

					if Area > TotalArea:		
						TotalArea = Area;

				## getting the smallest area
				elif DATAHASH['TYPE'].val == 4:

					if Area < TotalArea or a == 0:		
						TotalArea = Area;
						
			DATAHASH['ERROR'].val = 0;


		## calculating the average area
		if DATAHASH['TYPE'].val == 2:
			if DATAHASH['MODE'].val == 3:
				DATAHASH['SIZE'].val = TotalArea / len(me.verts);
			if DATAHASH['MODE'].val == 3:
				DATAHASH['SIZE'].val = TotalArea / (len(me.verts) * (len(me.verts) - 1));
			elif DATAHASH['MODE'].val == 2:
				DATAHASH['SIZE'].val = TotalArea / len(me.edges);
			else:
				DATAHASH['SIZE'].val = TotalArea / len(me.faces);
		else:
			DATAHASH['SIZE'].val = TotalArea;

		print "The", TypeList[DATAHASH['TYPE'].val], RelList[DATAHASH['REL'].val], ModeList[DATAHASH['MODE'].val], "of", DATAHASH['OBNAME'].val, "is", DATAHASH['SIZE'].val;
			
	## In case no mesh is selected
	else:
		DATAHASH['ERROR'].val = 1;
		
	DATAHASH['TYPETEXT'].val = DATAHASH['TYPE'].val;
	DATAHASH['MODETEXT'].val = DATAHASH['MODE'].val;
	DATAHASH['RELTEXT'].val = DATAHASH['REL'].val;


####################################################
# DRAW THE GUI                                     #
####################################################

def gui():


	#############################################################
	# Backgrounds                                               #
	#############################################################

	BGL.glClearColor(0.5, 0.5, 0.5, 0.0)
	BGL.glClear(BGL.GL_COLOR_BUFFER_BIT)

	BGL.glColor3f(0, 0, 0)			# Black background
	BGL.glRectf(1, 1, 269, 264)

	BGL.glColor3f(0.7, 0.7, 0.8)		# Light background
	BGL.glRectf(3, 3, 268, 263)

	BGL.glColor3f(0, 0, 0)			# Black Lowest
	BGL.glRectf(4, 4, 267, 105)
	
	BGL.glColor3f(0, 0, 0)			# Black Lowest
	BGL.glRectf(4, 106, 267, 126)
	
	BGL.glColor3f(0, 0, 0)			# Black Lowest
	BGL.glRectf(4, 127, 267, 220)

	BGL.glColor3f(0, 0, 0)			# Black Lowest
	BGL.glRectf(4, 221, 267, 241)
	
	BGL.glColor3f(0, 0, 0)			# Black Lowest
	BGL.glRectf(4, 242, 267, 262)
	
	#############################################################
	# TEXT                                                      #
	#############################################################	
	
	BGL.glColor3f(0.7, 0.7, 0.8)
	BGL.glRasterPos2d(20, 248)
	Draw.Text("Measure mesh ")
	Draw.Text(str(VERSION))
	Draw.Text("    (c) 2006 macouno")
	
	BGL.glColor3f(0.8, 0.9, 1.0)
	BGL.glRasterPos2d(20, 227)
	Draw.Text("measurement results & messages")
		
	## If the selected object is no mesh
	if DATAHASH['ERROR'].val != 0:
	
		BGL.glColor3f(1.0, 1.0, 1.0)
		BGL.glRasterPos2d(20, 200)
		Draw.Text(str(ErrorText[DATAHASH['ERROR'].val]))

	## If something has been measured
	elif DATAHASH['SIZE'].val != 0.0:

		BGL.glColor3f(0.7, 0.7, 0.8)
		BGL.glRasterPos2d(20, 200)
		Draw.Text("Object:")
		BGL.glColor3f(1.0, 1.0, 1.0)
		BGL.glRasterPos2d(70, 200)
		Draw.Text(str(DATAHASH['OBNAME'].val))		
		
		BGL.glColor3f(0.7, 0.7, 0.8)
		BGL.glRasterPos2d(20, 180)
		Draw.Text("Setting:")		
		BGL.glColor3f(1.0, 1.0, 1.0)
		BGL.glRasterPos2d(70, 180)
		Draw.Text(str(RelList[DATAHASH['RELTEXT'].val]))
		Draw.Text(", ")
		Draw.Text(str(TypeList[DATAHASH['TYPETEXT'].val]))
		
		BGL.glColor3f(0.7, 0.7, 0.8)
		BGL.glRasterPos2d(20, 160)
		Draw.Text("Mode:")
		BGL.glColor3f(1.0, 1.0, 1.0)
		BGL.glRasterPos2d(70, 160)
		Draw.Text(str(ModeList[DATAHASH['MODETEXT'].val]))
		
		BGL.glColor3f(0.7, 0.7, 0.8)
		BGL.glRasterPos2d(20, 140)
		Draw.Text("Result:")
		BGL.glColor3f(1.0, 1.0, 1.0)
		BGL.glRasterPos2d(70, 140)
		Draw.Text(str(DATAHASH['SIZE'].val))
		
	## If nothing has been measured yet
	else:
		BGL.glColor3f(1.0, 1.0, 1.0)
		BGL.glRasterPos2d(20, 200)
		Draw.Text("Select your object, and settings,")
		BGL.glRasterPos2d(20, 180)
		Draw.Text("and settings, and click on MEASURE.")
		BGL.glRasterPos2d(20, 160)
		Draw.Text("Results are in blender units")
		
	BGL.glColor3f(0.8, 0.9, 1.0)
	BGL.glRasterPos2d(20, 112)
	Draw.Text("settings")
	
	#############################################################
	# BUTTONS                                                   #
	#############################################################	
	
	## Mode dropdown menu
	types = "Measurement mode %t|Face surface area %x1|Edge length %x2|Distance between verts %x3|Distance from centre %x4|Uv surface area %x5"
	
	DATAHASH['MODE'] = Draw.Menu(types, 2, 20, 75, 230, 20, DATAHASH['MODE'].val, "What you want to measure.")
	
	if DATAHASH['MODE'].val != 5:
	
		## Relativity dropdown menu
		types = "Measurement relativity %t|Global %x1|Local %x2"
		
		DATAHASH['REL'] = Draw.Menu(types, 2, 20, 45, 110, 20, DATAHASH['REL'].val, "What do you want to measure relative to.")
	
	else:
	
		BGL.glColor3f(0.7, 0.7, 0.8)
		BGL.glRasterPos2d(20, 50)
		Draw.Text("Not available")

	## Measurement type dropdown menu
	types = "Measurement type %t|Total %x1|Average %x2|Largest %x3|Smallest %x4"
	
	DATAHASH['TYPE'] = Draw.Menu(types, 2, 140, 45, 110, 20, DATAHASH['TYPE'].val, "The type of measurement you want.")	
	
	## Reset gui button
	Draw.Button("MEASURE", 1, 20, 15, 110, 20, "Measure your currently selected object")
	
	## Exit button
	Draw.Button("EXIT", 5, 140, 15, 110, 20, "Exit the script!")
	
	
####################################################
# CHECK FOR THE ESCAPE KEY                         #
####################################################

def event(evt, val):
	if (evt == Draw.QKEY and not val): Draw.Exit()

####################################################
# ACTION AFTER THE BUTTON HAS BEEN PRESSED         #
####################################################

## Global DATAHASH
def bevent(evt):
	## Exit the script
	if (evt ==  1):
		getSizes()
		Draw.Redraw()
		
	## Redraw interface
	if (evt ==  2):
		Draw.Redraw()

	## Exit the script
	if (evt ==  5):
		Draw.Exit()
		
####################################################
# REGISTER THE FUNCTIONS                           #
####################################################

Draw.Register(gui, event, bevent)