#!BPY

"""
Name: 'BLenses'
Blender: 244
Group: 'Wizards'
Tooltip: 'Sets Blender camera field of view (FOV) to match a target lens focal length.'
"""

__author__ = "chipmasque"
__url__ = ("http://home.metrocast.net/~chipartist/BlensesSite/index.html")
__version__ = "0.0.1 30/10/07"

__bpydoc__ = """\
Usage: Sets the Blender camera's field-of-view (FOV) to match a lens of specified 
focal length. Other required parameters are camera image aperture (sometimes called 
"film width") and camera-to-subject distance (with an option for infinity focus). 
A world scale equivalency can be set for convenience of construction of Blender scenes 
matching real-world scenes. Optional camera controls include a point-of-interest (POI)
constraint at which the camera always points, easy interactive setting of camera 
DoFDist value, and an animation mode where focal lengths can be animated for more 
accurate zoom effects than using "Lens" Ipo curves. 

See the website for full documentation.
"""

#####################################################################################
# Matching Blender FOV to a real-world camera and lens
# (c) 2007 K. G. Nyman
# For more info see
# http://home.metrocast.net/~chipartist/BlensesSite
# Bug reports and comments: chipartist@metrocast.net
# Thanks to macouno for a starting point
#####################################################################################
# History
# V: 0.0.1 - 30-10-2007 - Version one public release
#####################################################################################
# ***** BEGIN GPL LICENSE BLOCK *****
#
# Script copyright (c) 2007 K. G. Nyman
#
# 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, maximum  02111-1307, USA.
#
# ***** END GPL LICENCE BLOCK *****
# --------------------------------------------------------------------------

import Blender
from Blender import *
import math

#####################################################################################
# FUNCTIONS

####################################################
# GUI drawing
def gui():
	
	if	NoCurCam:
		Draw.Exit()

	# Backgrounds
	BGL.glClearColor(0.5, 0.5, 0.5, 0.0)
	BGL.glClear(BGL.GL_COLOR_BUFFER_BIT)
	
	BGL.glColor3f(0.65, 0.65, 0.65) # Med grey 
	BGL.glRectf(5, 5, 385, 277)
	
	BGL.glColor3f(0.75, 0.75, 0.75)  # Lt grey
	BGL.glRectf(5, 23, 385, 247)

	# Text labels
	BGL.glColor3f(1, 1, 1) #Title
	BGL.glRasterPos2d(15, 257)
	Draw.Text("BLenses v."+VERSION, 'large')
	BGL.glRasterPos2d(155, 257)
	Draw.Text("(c) 2007 K. G. Nyman")

	BGL.glColor3f(0, 0, 0) #black
	
	BGL.glRasterPos2d(15, 10) # FOV readout
	Draw.Text(DATA_DICT['FOV_TEXT'].val) 
	
	BGL.glRasterPos2d(205, 10)  # Render Dimensions readout
	Draw.Text(DATA_DICT['REND_TXT'].val)

	BGL.glRasterPos2d(300, 232)  # World Scale Unit toggles label
	Draw.Text("World Scale Unit", 'small')
	BGL.glRasterPos2d(300, 222)
	Draw.Text("( = 1 B.U.)", 'small')

	BGL.glRasterPos2d(95, 91)  # POI warning text
	Draw.Text("<-- This will replace any existing TrackTo", 'small')
	BGL.glRasterPos2d(95, 81)  
	Draw.Text("constraint & target on the current camera", 'small')
		
	# GUI controls	
	# Input fields
	DATA_DICT['LENS_FL'] = Draw.Number("Lens Focal Length", 0, 15, 220, 180, 20, DATA_DICT['LENS_FL'].val, 1, 250, "Target lens focal length (mm)")	
	DATA_DICT['IMG_AP_W'] = Draw.Number("Image Aperture Width", 0, 15, 195, 180, 20, DATA_DICT['IMG_AP_W'].val, 1, 260, "Width of exposed camera image (mm)")	
	DATA_DICT['IMG_AP_H'] = Draw.Number("Image Aperture Height", 0, 195, 195, 180, 20, DATA_DICT['IMG_AP_H'].val, 1, 260, "Height of exposed camera image (mm)")
	DATA_DICT['IMG_AR'] = Draw.Number("Image Aspect Ratio", 0, 15, 175, 180, 20, DATA_DICT['IMG_AR'].val, 1, 5, "Camera image aspect ratio direct input")	
	DATA_DICT['SUBJ_DIST'] = Draw.Number("Camera to Subject", 0, 15, 130, 180, 20, DATA_DICT['SUBJ_DIST'].val, 1, 5000, "Distance from camera to point of sharpest focus")
	DATA_DICT['REN_W'] = Draw.Number("Render Width", 0, 15, 30, 180, 20, DATA_DICT['REN_W'].val, 1, 30000, "Width of rendered image")	

	# Buttons
	Draw.PushButton("QUIT", 11, 325, 251, 50, 20, "Exit the script")
	Draw.PushButton("Calculate & Set Image A.R.", 7, 195, 175, 180, 20, "Set the camera image aspect ratio")
	Draw.PushButton("Image Aperture / Camera Profile Presets", 2, 15, 155, 360, 20, "Precalculated data for various cameras and film formats")
	Draw.PushButton("Calculate Render Size", 6, 195, 30, 180, 20, "Optionally calculate render height from A.R.")
	Draw.PushButton("Calculate & Set FOV", 1, 15, 105, 180, 20, "Set matching FOV")
	Draw.PushButton("Create POI", 12, 15, 80, 75, 20, "Required for Interactive FOV and animation")
	Draw.PushButton("Delete POI", 17, 300, 80, 75, 20, "Required for Interactive FOV and animation")	
	Draw.PushButton("Anim'n Cancel", 15, 110, 55, 95, 20, "Removes the animation control system")
	Draw.PushButton("Save Config", 16, 215, 55, 80, 20, "Save current data to memory & disk")
	Draw.PushButton("Load Config", 13, 295, 55, 80, 20, "Load saved data from memory & disk")
	
	# Toggles
	DATA_DICT['WSU_MM'] = Draw.Toggle("mm", 8, 205, 221, 30, 18, DATA_DICT['WSU_MM'].val, "Set World Scale Units to millimeters")
	DATA_DICT['WSU_CM'] = Draw.Toggle("cm", 9, 235, 221, 30, 18, DATA_DICT['WSU_CM'].val, "Set World Scale Units to centimeters")
	DATA_DICT['WSU_M'] = Draw.Toggle("m", 10, 265, 221, 30, 18, DATA_DICT['WSU_M'].val, "Set World Scale Units to meters")
	DATA_DICT['FIXED_FOV'] = Draw.Toggle("Fixed", 3, 205, 106, 85, 18, DATA_DICT['FIXED_FOV'].val, "FOV remains fixed once set")
	DATA_DICT['INTER_FOV'] = Draw.Toggle("Interactive", 4, 290, 106, 85, 18, DATA_DICT['INTER_FOV'].val, "FOV is calculated interactively")
	DATA_DICT['INFIN_FOC'] = Draw.Toggle("Use Infinity Focus", 5, 195, 130, 180, 20, DATA_DICT['INFIN_FOC'].val, "Subject distance is greater than 30 meters")
	DATA_DICT['ANIM_MODE'] = Draw.Toggle("Anim'n Mode", 14, 15, 55, 95, 20, DATA_DICT['ANIM_MODE'].val, "Enables a control system for animating focal length")
	
####################################################
# Button & toggle event handlers
def bevent(evt):
	
	# Calculate & Set FOV button
	if   (evt == 1):
		if	InAnimMode:
			InterFOV()
			return
	
		CalcFOV()
		return

	# Presets & profiles
	elif   (evt == 2):
		setProfile()
		return

	# Enable Fixed FOV
	elif   (evt == 3):
		DATA_DICT['FIXED_FOV'].val = 1
		DATA_DICT['INTER_FOV'].val = 0

	# Enable Interactive FOV
	elif   (evt == 4):
		if	(DATA_DICT['INFIN_FOC'].val == 1):
			DATA_DICT['FIXED_FOV'].val = 1
			DATA_DICT['INTER_FOV'].val = 0
		else:
			DATA_DICT['FIXED_FOV'].val = 0
			DATA_DICT['INTER_FOV'].val = 1
			InterFOV()
			return	

	# Enable Use Infinity Focus
	elif   (evt == 5):
		if	(DATA_DICT['INFIN_FOC'].val == 1):
			DATA_DICT['FIXED_FOV'].val = 1
			DATA_DICT['INTER_FOV'].val = 0

	# Calculate Render Size button
	elif   (evt == 6):
		CalcRenderSize()

	# Calculate & Set Image A.R button
	elif   (evt == 7):
		CalcImageAR()

	# World Scale Units = millimeters
	elif   (evt == 8):
		DATA_DICT['WSU_MM'].val = 1
		DATA_ANIM['WSU_MM'] = 1
		DATA_DICT['WSU_CM'].val = 0
		DATA_DICT['WSU_M'].val = 0
		saveAnimData()

	# World Scale Units = centimeters
	elif   (evt == 9):
		DATA_DICT['WSU_MM'].val = 0
		DATA_DICT['WSU_CM'].val = 1
		DATA_ANIM['WSU_CM'] = 1
		DATA_DICT['WSU_M'].val = 0
		saveAnimData()

	# World Scale Units = meters
	elif   (evt == 10):
		DATA_DICT['WSU_MM'].val = 0
		DATA_DICT['WSU_CM'].val = 0
		DATA_DICT['WSU_M'].val = 1
		DATA_ANIM['WSU_M'] = 1
		saveAnimData()

	# The QUIT button
	elif   (evt == 11):
		PupText = "Save current data?%t|Yes|No"
		Response = Draw.PupMenu(PupText)
		if	(Response == 1):
			doSave = saveConfig()
			if (doSave != True):
				return
		Draw.Exit()
		return

	# Create Point of Interest
	elif   (evt == 12):
		makePOI()
		return

	# Delete Point of Interest
	elif   (evt == 17):
		if checkPOI():
			PupText = "Delete POI for current camera?%t|Yes|No"
			Response = Draw.PupMenu(PupText)
			if	(Response == 1):
				deletePOI()
			return
	
	# Save a config file
	elif   (evt == 16):
		saveConfig()
		return

	# Load a config file
	elif   (evt == 13):
		loadConfig()
		return

	# Enable Animation Mode
	elif   (evt == 14):
		checkAnimMode()
		if 	InAnimMode:
			DATA_DICT['ANIM_MODE'].val = 1
			Draw.Redraw()
			return
		doAnimSetup()
		return

	# Disable Animation Mode
	elif   (evt == 15):
		undoAnimSetup()
		return

	else:
		pass
		
	Draw.Redraw()

####################################################
# find the current Camera
def getCurCam():
	global curScn, curCam, curCamData
	
	curScn = Scene.GetCurrent()
	curCam = curScn.objects.camera # the current camera
	curCamData = curCam.getData() # the camera data object

###########################################################
# check for POI on current camera
def checkPOI():
	CamConstraints = curCam.constraints
	CamConstLen = CamConstraints.__len__()
	if	(CamConstLen > 0):
		for	CamConst in CamConstraints:
			if	(CamConst.type == Constraint.Type.TRACKTO):
				if CamConst.name.startswith('POI_'+curCam.name):
					return CamConst
	else:
		return None

####################################################
# message pop-up: missing POI constraint
def MissingPOI():
	PupText = "Requires a Point of Interest constraint%t|OK"
	Draw.PupMenu(PupText)
	DATA_DICT['INTER_FOV'].val = 0
	DATA_DICT['FIXED_FOV'].val = 1
	Draw.Redraw()
	
####################################################
# calculate image aspect ratio of target camera
def CalcImageAR():
	ImgW = DATA_DICT['IMG_AP_W'].val
	ImgH = DATA_DICT['IMG_AP_H'].val
	ImgAR = ImgW / ImgH
	DATA_DICT['IMG_AR'].val = ImgAR
	Draw.Redraw()

####################################################	
# optional render size calculation
def CalcRenderSize():
	RenW = DATA_DICT['REN_W'].val
	RenAR = DATA_DICT['IMG_AR'].val
	RenH = int(RenW / RenAR)
	DATA_DICT['REND_TXT'].val = "Render Size = " + str(RenW) + "x" + str(RenH)
	Draw.Redraw()
	
####################################################
# Determine if Animation is enabled
def checkAnimMode():
	global InAnimMode
	
	getCurCam()
	
	CamScLinks = curCam.getScriptLinks('Redraw')
	if (CamScLinks == []):
		InAnimMode = False
		return

	for ScLink in CamScLinks:
		if	ScLink.startswith('BLensAnim'):
			InAnimMode = True
		else:
			InAnimMode = False

####################################################
# the core focal length to FOV calculation
def CalcFOV():
	
	# find the current camera (for multiple camera usage)
	getCurCam()
	# set some camera & GUI values:
	# make markers visible
	curCamData.drawLimits = 1
	# DoFDist serves as Camera to Subject distance
	curCamData.dofDist = DATA_DICT['SUBJ_DIST'].val 
	# make sure clip is > DoFDist
	if	(curCamData.clipEnd < curCamData.dofDist):
		curCamData.clipEnd = curCamData.dofDist + 50
	
	# set World Scale Unit multiplier
	if	(DATA_DICT['WSU_M'] == 1):
		WorldScale = 1000
	elif (DATA_DICT['WSU_CM'] == 1):
		WorldScale = 10
	else:
		WorldScale = 1

	# NOTE: uses variable names per website formula
	# camera to subject distance
	s = curCamData.dofDist * WorldScale # convert to millimeters
	# camera image aperture width (film/sensor wodth)
	A = DATA_DICT['IMG_AP_W'].val
	# target lens focal length
	f = DATA_DICT['LENS_FL'].val
	
	# Use Infinity Focus is enabled: basic FOV formula
	if	(DATA_DICT['INFIN_FOC'].val == 1):
		FOV = 2 * math.atan(A/(2 * f)) * 180/math.pi

	# this formula accounts for magnification
	else:
		FOV = 2 * math.atan((A*(s-f))/(2 * s * f)) * 180/math.pi
	
	#set the Blender camera angle (field of view)
	curCamData.angle = FOV
	
	# report the calculated FOV
	DATA_DICT['FOV_TEXT'].val = "FOV = " + str(FOV)
	
	# hack to update Camera>Lens setting (inaccurate as a focal length)
	curLens = curCamData.lens
	curCamData.lens = curLens
	
	if 	InAnimMode:
		saveAnimData() # update for animation mode
	
	Window.RedrawAll() # insure 3D windows are updated

####################################################
# calculate FOV interactively based on positions of Camera and POI
def InterFOV():

	getCurCam()
	
	CamConst = checkPOI()
	if CamConst:
		CamPOITarg = CamConst.__getitem__(Constraint.Settings.TARGET)
	else:
		MissingPOI()
		return

	curCamLoc = curCam.getLocation('worldspace')
	curCamPOILoc = CamPOITarg.getLocation('worldspace')	
	#break down tuples (done mainly for clarity of scripting)
	curCamLocX = curCamLoc[0]
	curCamLocY = curCamLoc[1]
	curCamLocZ = curCamLoc[2]
	curCamPOILocX = curCamPOILoc[0]
	curCamPOILocY = curCamPOILoc[1]
	curCamPOILocZ = curCamPOILoc[2]
	# find absolute differences
	X1 = abs(curCamLocX - curCamPOILocX)
	Y1 = abs(curCamLocY - curCamPOILocY)
	Z1 = abs(curCamLocZ - curCamPOILocZ)
	# Pythagorean math finds absolute distance between Camera & POI
	Hyp1 = math.sqrt(math.pow(X1, 2) + math.pow(Z1, 2))
	Hyp2 = math.sqrt(math.pow(Hyp1, 2) + math.pow(Y1, 2))
	
	# set new Camera to Subject distance 
	DATA_DICT['SUBJ_DIST'].val = Hyp2
	
	if 	InAnimMode:
		saveAnimData() # update for animation mode
	
	# recalculate the FOV, also resets DoFDist
	CalcFOV()

####################################################
# create point-of-interest constraint on Camera
def makePOI():
	global NoPOI
	
	getCurCam()
	
	curCamData.dofDist = DATA_DICT['SUBJ_DIST'].val
	
	# check constraints list of current camera
	CamConstraints = curCam.constraints
	CamConstLen = CamConstraints.__len__()
	if	(CamConstLen > 0):
		for	CamConst in CamConstraints:
			# if a TrackTo already exists it must be removed
			if	(CamConst.type == Constraint.Type.TRACKTO):
				PupText = "Replace existing TrackTo constraint & target on "+ curCam.name + "?%t|Yes|No"
				Response = Draw.PupMenu(PupText)
				if	(Response != 1):
					return
				if	(CamConst.__getitem__(Constraint.Settings.TARGET)):
					deletePOI()

				CamConstLen = CamConstraints.__len__()

	#create the constraint and its target Empty
	CamConstraints.append(Constraint.Type.TRACKTO)
	CamPOI = CamConstraints.__getitem__(CamConstLen)
	CamPOI.name = "POI_" + curCam.name
	
	POITargName = curCam.name + "POI"

	# create and position Empty as POI constraint target
	CamPOITarg = curScn.objects.new('Empty', POITargName)
	curCamLoc = curCam.getLocation('worldspace')
	POI_ZLoc = curCamLoc[2]
	POI_ZLoc -= curCamData.dofDist
	CamPOITarg.setLocation(curCamLoc[0], curCamLoc[1], POI_ZLoc)
	
	# constraint settings
	CamPOI.__setitem__(Constraint.Settings.TARGET, CamPOITarg)
	CamPOI.__setitem__(Constraint.Settings.UP, Constraint.Settings.UPY)
	CamPOI.__setitem__(Constraint.Settings.TRACK, Constraint.Settings.TRACKNEGZ)
	
	NoPOI = False
	
	Window.RedrawAll() # insure 3D windows are updated
	
####################################################
# remove POI constraint & target
def deletePOI():	
	global NoPOI
	
	getCurCam()
	
	CamConst = checkPOI()
	if CamConst:
		NoPOI = True
		if InAnimMode:
			PupText = "This will cancel Animation Mode. Continue?%t|Yes|No"
			Response =Draw.PupMenu(PupText)
			if	(Response == 1):
				undoAnimSetup()
			else:
				return
		DelTarg = CamConst.__getitem__(Constraint.Settings.TARGET)
		DelTarg.name = ""
		curScn.objects.unlink(DelTarg)
		curCam.constraints.remove(CamConst)
		
	DATA_DICT['INTER_FOV'].val = 0
	DATA_DICT['FIXED_FOV'].val = 1
	
	Window.RedrawAll() # insure 3D windows are updated

######################################################
# set up for focal length/FOV animation
def doAnimSetup():
	global InAnimMode
	
	NoGo = False #initialize
	
	getCurCam()
	
	# first check for POI (needed for scriptlink)
	CamConst = checkPOI()
	if CamConst:
		pass
	else:
		MissingPOI()
		NoGo = True
		DATA_DICT['ANIM_MODE'].val = 0
		Draw.Redraw()
		return
		
	# script for link is not present
	if not HaveScrLink:
		PupText = "\"BLensAnim.py\" not found -- see documentation%t|OK"
		Draw.PupMenu(PupText)
		NoGo = True
		DATA_DICT['ANIM_MODE'].val = 0
		Draw.Redraw()
		return
		
	PupText = "Create Zoom proxy & make scriptlinks? (See documentation)%t|Yes|No"
	Response = Draw.PupMenu(PupText)
	if	(Response != 1):
		DATA_DICT['ANIM_MODE'].val = 0
		Draw.Redraw()	
		return

	# save data needed for animation scriptlink
	saveAnimData()
	
	# create animatable control object for zoom (delta FOV)
	ZoomerName = "Zoom_" + curCam.name
	Zoomer = curScn.objects.new('Empty', ZoomerName)
	# pick up focal length from curent settings
	ZoomerScale = DATA_DICT['LENS_FL'].val
	Zoomer.size = (ZoomerScale, ZoomerScale, ZoomerScale)
	Zoomer.drawSize = (10 / Zoomer.SizeX) #keeps draw size manageable
	Zoomer.setLocation(curCam.getLocation())

	# establish scriptlinks
	if not NoGo:
		curCam.addScriptLink('BLensAnim.py', "FrameChanged")
		curCam.addScriptLink('BLensAnim.py', "Redraw")
		InAnimMode = True
		
	Window.RedrawAll() # insure 3D windows are updated

######################################################
# disable focal length/FOV animation
def undoAnimSetup():
	global InAnimMode
	Response = 0 # initialize
	
	getCurCam()
	
	ScnObjs = curScn.objects
	for obj in ScnObjs:
		if (obj.name == "Zoom_"+curCam.name):
			ZoomDel = obj
			if	not NoPOI:
				PupText = "Remove "+ZoomDel.name+" and scriptlinks on current camera?%t|Yes|No"
				Response = Draw.PupMenu(PupText)
			if	(Response == 1) or NoPOI:
				#remove BLenses scriptlinks
				CamScLinks = curCam.getScriptLinks('Redraw')
				for	ScLink in CamScLinks:
					if	ScLink.startswith('BLensAnim'):
						LinkList =[ScLink]
						curCam.clearScriptLinks(LinkList)
						Window.RedrawAll() # insure 3D windows are updated
				CamScLinks = curCam.getScriptLinks('FrameChanged')
				for	ScLink in CamScLinks:
					if	ScLink.startswith('BLensAnim'):
						LinkList =[ScLink]
						curCam.clearScriptLinks(LinkList)
						Window.RedrawAll() # insure 3D windows are updated

				#remove Zoom proxy
				ZoomDel.name = ""
				curScn.objects.unlink(ZoomDel)

				DATA_DICT['ANIM_MODE'].val = 0
				Draw.Redraw()
				InAnimMode = False

			else:
				return
	
####################################################
# load previously saved data from config file
def loadConfig():
	global LastConfig
	
	# check for Registry values
	ConfigFile = Draw.PupStrInput("", LastConfig, 30)

	DATA_CACHED = Blender.Registry.GetKey(ConfigFile, True)
	if	DATA_CACHED:
		DATA_DICT['SUBJ_DIST'].val = DATA_CACHED['SUBJ_DIST']
		DATA_DICT['REN_W'].val = DATA_CACHED['REN_W']
		DATA_DICT['IMG_AR'].val = DATA_CACHED['IMG_AR']
		DATA_DICT['IMG_AP_W'].val = DATA_CACHED['IMG_AP_W']
		DATA_DICT['LENS_FL'].val = DATA_CACHED['LENS_FL']	
		DATA_DICT['FIXED_FOV'].val = DATA_CACHED['FIXED_FOV']
		DATA_DICT['INTER_FOV'].val = DATA_CACHED['INTER_FOV']
		DATA_DICT['INFIN_FOC'].val = DATA_CACHED['INFIN_FOC']
		DATA_DICT['IMG_AP_H'].val = DATA_CACHED['IMG_AP_H']
		DATA_DICT['WSU_MM'].val = DATA_CACHED['WSU_MM']
		DATA_DICT['WSU_CM'].val = DATA_CACHED['WSU_CM']
		DATA_DICT['WSU_M'].val = DATA_CACHED['WSU_M']
		DATA_DICT['FOV_TEXT'].val = DATA_CACHED['FOV_TEXT']
		DATA_DICT['REND_TXT'].val = DATA_CACHED['REND_TXT']
		DATA_DICT['ANIM_MODE'].val = DATA_CACHED['ANIM_MODE']
		
		if	InAnimMode:
			DATA_DICT['ANIM_MODE'].val = 1
		else:
			DATA_DICT['ANIM_MODE'].val = 0
			
		LastConfig = ConfigFile
		
		Draw.Redraw()
					
####################################################
# Save existing BLenses settings
def saveConfig():
	global LastConfig
	
	getCurCam()
	
	ConfigName = curCam.name + "_Data"
	saveName = Draw.PupStrInput("", ConfigName, 30)
	if	Blender.Registry.GetKey(saveName, True):
		PupText = "Overwrite " + saveName +"?%t|Yes|No"
		Response = Draw.PupMenu(PupText)
		if	(Response != 1):
			return False
	
	DATA_SAVE = {}
	DATA_SAVE['SUBJ_DIST'] = DATA_DICT['SUBJ_DIST'].val
	DATA_SAVE['REN_W'] = DATA_DICT['REN_W'].val
	DATA_SAVE['IMG_AR'] = DATA_DICT['IMG_AR'].val
	DATA_SAVE['IMG_AP_W'] = DATA_DICT['IMG_AP_W'].val
	DATA_SAVE['LENS_FL'] = DATA_DICT['LENS_FL'].val
	DATA_SAVE['FIXED_FOV'] = DATA_DICT['FIXED_FOV'].val
	DATA_SAVE['INTER_FOV'] = DATA_DICT['INTER_FOV'].val
	DATA_SAVE['INFIN_FOC'] = DATA_DICT['INFIN_FOC'].val
	DATA_SAVE['IMG_AP_H'] = DATA_DICT['IMG_AP_H'].val
	DATA_SAVE['WSU_MM'] = DATA_DICT['WSU_MM'].val
	DATA_SAVE['WSU_CM'] = DATA_DICT['WSU_CM'].val
	DATA_SAVE['WSU_M'] = DATA_DICT['WSU_M'].val
	DATA_SAVE['FOV_TEXT'] = DATA_DICT['FOV_TEXT'].val
	DATA_SAVE['REND_TXT'] = DATA_DICT['REND_TXT'].val
	DATA_SAVE['ANIM_MODE'] = DATA_DICT['ANIM_MODE'].val
	
	Blender.Registry.SetKey(saveName, DATA_SAVE, True)
	LastConfig = saveName
	
	return True

#####################################################
# load Presets & Profiles data
def setProfile():
	
	try:
		# update config file if it's found in Text Editor
		# (presumably for editing)
		BLensProfs = Text.Get('BLensProfiles.cfg')
		Registry.RemoveKey('BLensProfiles')
		Registry.GetKey('BLensProfiles', True)
	except:
		pass
	
	ProfText = "" # initialize
	PROFILES = Blender.Registry.GetKey('BLensProfiles', True)
	# warn of missing file
	if not PROFILES:
		PupText = "Missing \"BLensProfiles.cfg\" -- see documentation%t|OK"
		Draw.PupMenu(PupText)
		return
	# build the initial Profiles string from Registry keys
	for Profile in PROFILES:
		if Profile.isdigit():
			#add PupMenu index numeral
			ProfText = ProfText + "%x" + Profile +" "
		# structure data for PupMenu	
		ProfText = ProfText + PROFILES[Profile] + "|"
		
	#sort PupMenu items by index numeral
	count = 0
	ProfSort = ""
	Title = ""
	LineNum = 0
	PupEntries = ProfText.rsplit('|')
	for Entries in PupEntries:
		for Line in PupEntries:
			Line2 = Line.lstrip('%x')
			Line3 = Line2.partition(" ")
			if	Line3[0].isdigit():
				LineNum = int(Line3[0])
				if 	(LineNum == count):
					ProfSort = ProfSort + Line + "|"
					count += 1
			else:
				if	Title == "":
					Title = Line
	
	SortProf = Title +"|"+ ProfSort

	# draw the pop-up menu
	Response = Draw.PupMenu(SortProf)
	for Profile in PROFILES:
		if (Profile.isdigit() and int(Profile) == Response):
			if	(int(Profile) == 0):
				PupText = "Edit profiles in the Text Editor. File is \"BLensProfiles.cfg\"%t|OK"
				Draw.PupMenu(PupText)
				# check if already loaded
				try:
					IsLoaded = Text.Get('BLensProfiles.cfg')
					return
				except:
					BlendScript = Blender.Get('scriptsdir')
					ProfConfig = BlendScript + "\\bpydata\config\BLensProfiles.cfg"
					Text.Load(ProfConfig)
					return
			
			ProfLine = PROFILES[Profile]
			# parse the profile entry
			ProfData = ProfLine.rsplit(':', 3)
			ProfApW = ProfData[1]
			ProfApH = ProfData[2]
			ProfAR = ProfData[3]
			DATA_DICT['IMG_AP_W'].val = float(ProfApW)
			DATA_DICT['IMG_AP_H'].val = float(ProfApH)
			DATA_DICT['IMG_AR'].val = float(ProfAR)
			
			Draw.Redraw()
			
##########################################################
# save animation data set
def saveAnimData():

	# update focal length value on zoom proxy
	ScnObjs = curScn.objects
	for obj in ScnObjs:
		if (obj.name == "Zoom_"+curCam.name):
			Zoomer = obj
			ZoomerScale = DATA_DICT['LENS_FL'].val
			Zoomer.size = (ZoomerScale, ZoomerScale, ZoomerScale)
		
	DATA_ANIM['IMG_AP_W'] = DATA_DICT['IMG_AP_W'].val
	DATA_ANIM['WSU_MM'] = DATA_DICT['WSU_MM'].val
	DATA_ANIM['WSU_CM'] = DATA_DICT['WSU_CM'].val
	DATA_ANIM['WSU_M'] = DATA_DICT['WSU_M'].val
	
	Blender.Registry.SetKey('BLensAnimData', DATA_ANIM, True)
	
	Window.RedrawAll() # insure 3D windows are updated
	
#####################################################################################
# SETUP & GLOBALS

VERSION = '0.0.1'
LastConfig = " " #initialize
HaveScrLink = False #initialize
NoPOI = True # initialize
InAnimMode = False # initialize

# Get scene data
curScn = Scene.GetCurrent()
try:
	getCurCam()
	NoCurCam = False
	if	(curCamData.dofDist != 0):
		curDofDist = curCamData.dofDist
	else:
		curCamData.dofDist = 100 # prevents div by zero at startup
		curDofDist = curCamData.dofDist

	# detect Animation Mode by presence of scriptlinks
	checkAnimMode()

	# is there a POI on the current camera?
	NoPOI = not checkPOI()
		
except:
	curDofDist = 100
	InAnimMode = False
	NoCurCam = True
	PupText1 = "Script requires a current camera to run.%t|OK"
	Draw.PupMenu(PupText1)
	PupText2 = "Please select a camera and make it the current camera.%t|OK"
	Draw.PupMenu(PupText2)

# load script for scriptlinks (Animation mode)
ScrDir = Blender.Get('scriptsdir')
curTexts = Text.Get()
if	curTexts == []:
	try:
		Text.Load(ScrDir+"\BLensAnim.py")
		HaveScrLink = True
	except:
		HaveScrLink = False
		PupText = "\"BLensAnim.py\" not found -- see documentation%t|OK"
		Draw.PupMenu(PupText)

else:
	for text in curTexts:
		if	text.name.startswith('BLensAnim'):
			HaveScrLink = True
			break
	try:
		Text.Load(ScrDir+"\BLensAnim.py")
		HaveScrLink = True
	except:
		HaveScrLink = False
		PupText = "\"BLensAnim.py\" not found -- see documentation%t|OK"
		Draw.PupMenu(PupText)
		
# GUI defaults dictionary
DATA_DICT = {
	'SUBJ_DIST': Draw.Create(curDofDist), #Cam to subj distance (World Scale Units)
	'REN_W': Draw.Create(720), #Render width (pixels)
	'IMG_AR': Draw.Create(1.50),  #Camera image aspect ratio
	'IMG_AP_W': Draw.Create(36.0),  #Camera image width (mm)
	'LENS_FL': Draw.Create(50.0),  #Target lens focal length (mm)	
	'FIXED_FOV': Draw.Create(1),  #Fixed is on by default
	'INTER_FOV': Draw.Create(0),  #Interactive is off by default
	'INFIN_FOC': Draw.Create(0),  #Infinity Focus is off by default
	'IMG_AP_H': Draw.Create(24.0),  #Camera image height (mm)
	'WSU_MM': Draw.Create(1),  #World Scale Units default is millimeters
	'WSU_CM': Draw.Create(0),  #World Scale Units centimeters option
	'WSU_M': Draw.Create(0),  #World Scale Units meters option
	'FOV_TEXT': Draw.Create("FOV = "), #FOV report field
	'REND_TXT': Draw.Create("Render Size = "), #Render Size report field
	'ANIM_MODE': Draw.Create(0),  #Animation mode, off by default
       }

if	InAnimMode:
	DATA_DICT['ANIM_MODE'].val = 1
	Draw.Redraw()

DATA_ANIM = {} # dictionary for animation use

# Register gui & event trapping functions
Draw.Register(gui, event, bevent)

# Escape keytrap		
#def event(evt, val):
#	if	(evt == Draw.ESCKEY and not val):
#		pass
