from __future__ import absolute_import
import __init__

import wx, os, platform, types, webbrowser, math, subprocess, threading, time, re
import ConfigParser

from wx import glcanvas
import wx
try:
	import OpenGL
	OpenGL.ERROR_CHECKING = False
	from OpenGL.GLU import *
	from OpenGL.GL import *
	hasOpenGLlibs = True
except:
	print "Failed to find PyOpenGL: http://pyopengl.sourceforge.net/"
	hasOpenGLlibs = False

from gui import opengl
from gui import toolbarUtil
from gui import icon
from gui import configBase
from gui import validators
from gui import printWindow
from util import profile
from util import util3d
from util import stl
from util import sliceRun
from util import gcodeInterpreter
from util import exporer

class Action(object):
	pass

class ProjectObject(stl.stlModel):
	def __init__(self, parent, filename):
		super(ProjectObject, self).__init__()

		self.load(filename)

		self.parent = parent
		self.filename = filename
		self.scale = 1.0
		self.rotate = 0.0
		self.flipX = False
		self.flipY = False
		self.flipZ = False
		self.swapXZ = False
		self.swapYZ = False
		self.extruder = 0
		self.profile = None
		
		self.modelDisplayList = None
		self.modelDirty = False

		self.origonalVertexes = list(self.vertexes)
		for i in xrange(0, len(self.origonalVertexes)):
			self.origonalVertexes[i] = self.origonalVertexes[i].copy()
		self.getMinimumZ()
		
		self.centerX = -self.getMinimum().x + 5
		self.centerY = -self.getMinimum().y + 5
		
		self.updateModelTransform()

		self.centerX = -self.getMinimum().x + 5
		self.centerY = -self.getMinimum().y + 5

	def isSameExceptForPosition(self, other):
		if self.filename != other.filename:
			return False
		if self.scale != other.scale:
			return False
		if self.rotate != other.rotate:
			return False
		if self.flipX != other.flipX:
			return False
		if self.flipY != other.flipY:
			return False
		if self.flipZ != other.flipZ:
			return False
		if self.swapXZ != other.swapXZ:
			return False
		if self.swapYZ != other.swapYZ:
			return False
		if self.extruder != other.extruder:
			return False
		if self.profile != other.profile:
			return False
		return True

	def updateModelTransform(self):
		rotate = self.rotate / 180.0 * math.pi
		scaleX = 1.0
		scaleY = 1.0
		scaleZ = 1.0
		if self.flipX:
			scaleX = -scaleX
		if self.flipY:
			scaleY = -scaleY
		if self.flipZ:
			scaleZ = -scaleZ
		swapXZ = self.swapXZ
		swapYZ = self.swapYZ
		mat00 = math.cos(rotate) * scaleX
		mat01 =-math.sin(rotate) * scaleY
		mat10 = math.sin(rotate) * scaleX
		mat11 = math.cos(rotate) * scaleY
		
		for i in xrange(0, len(self.origonalVertexes)):
			x = self.origonalVertexes[i].x
			y = self.origonalVertexes[i].y
			z = self.origonalVertexes[i].z
			if swapXZ:
				x, z = z, x
			if swapYZ:
				y, z = z, y
			self.vertexes[i].x = x * mat00 + y * mat01
			self.vertexes[i].y = x * mat10 + y * mat11
			self.vertexes[i].z = z * scaleZ

		for face in self.faces:
			v1 = face.v[0]
			v2 = face.v[1]
			v3 = face.v[2]
			face.normal = (v2 - v1).cross(v3 - v1)
			face.normal.normalize()

		minZ = self.getMinimumZ()
		minV = self.getMinimum()
		maxV = self.getMaximum()
		for v in self.vertexes:
			v.z -= minZ
			v.x -= minV.x + (maxV.x - minV.x) / 2
			v.y -= minV.y + (maxV.y - minV.y) / 2
		self.getMinimumZ()
		self.modelDirty = True
	
	def clone(self):
		p = ProjectObject(self.parent, self.filename)

		p.centerX = self.centerX + 5
		p.centerY = self.centerY + 5
		
		p.filename = self.filename
		p.scale = self.scale
		p.rotate = self.rotate
		p.flipX = self.flipX
		p.flipY = self.flipY
		p.flipZ = self.flipZ
		p.swapXZ = self.swapXZ
		p.swapYZ = self.swapYZ
		p.extruder = self.extruder
		p.profile = self.profile
		
		p.updateModelTransform()
		
		return p
	
	def clampXY(self):
		if self.centerX < -self.getMinimum().x * self.scale + self.parent.extruderOffset[self.extruder].x:
			self.centerX = -self.getMinimum().x * self.scale + self.parent.extruderOffset[self.extruder].x
		if self.centerY < -self.getMinimum().y * self.scale + self.parent.extruderOffset[self.extruder].y:
			self.centerY = -self.getMinimum().y * self.scale + self.parent.extruderOffset[self.extruder].y
		if self.centerX > self.parent.machineSize.x + self.parent.extruderOffset[self.extruder].x - self.getMaximum().x * self.scale:
			self.centerX = self.parent.machineSize.x + self.parent.extruderOffset[self.extruder].x - self.getMaximum().x * self.scale
		if self.centerY > self.parent.machineSize.y + self.parent.extruderOffset[self.extruder].y - self.getMaximum().y * self.scale:
			self.centerY = self.parent.machineSize.y + self.parent.extruderOffset[self.extruder].y - self.getMaximum().y * self.scale

class projectPlanner(wx.Frame):
	"Main user interface window"
	def __init__(self):
		super(projectPlanner, self).__init__(None, title='Cura - Project Planner')
		
		wx.EVT_CLOSE(self, self.OnClose)
		self.panel = wx.Panel(self, -1)
		self.SetSizer(wx.BoxSizer(wx.VERTICAL))
		self.GetSizer().Add(self.panel, 1, flag=wx.EXPAND)
		#self.SetIcon(icon.getMainIcon())
		
		self.list = []
		self.selection = None

		self.machineSize = util3d.Vector3(profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height'))
		self.headSizeMin = util3d.Vector3(profile.getPreferenceFloat('extruder_head_size_min_x'), profile.getPreferenceFloat('extruder_head_size_min_y'),0)
		self.headSizeMax = util3d.Vector3(profile.getPreferenceFloat('extruder_head_size_max_x'), profile.getPreferenceFloat('extruder_head_size_max_y'),0)

		self.extruderOffset = [
			util3d.Vector3(0,0,0),
			util3d.Vector3(profile.getPreferenceFloat('extruder_offset_x1'), profile.getPreferenceFloat('extruder_offset_y1'), 0),
			util3d.Vector3(profile.getPreferenceFloat('extruder_offset_x2'), profile.getPreferenceFloat('extruder_offset_y2'), 0),
			util3d.Vector3(profile.getPreferenceFloat('extruder_offset_x3'), profile.getPreferenceFloat('extruder_offset_y3'), 0)]

		self.toolbar = toolbarUtil.Toolbar(self.panel)

		toolbarUtil.NormalButton(self.toolbar, self.OnLoadProject, 'open.png', 'Open project')
		toolbarUtil.NormalButton(self.toolbar, self.OnSaveProject, 'save.png', 'Save project')
		self.toolbar.AddSeparator()
		group = []
		toolbarUtil.RadioButton(self.toolbar, group, 'object-3d-on.png', 'object-3d-off.png', '3D view', callback=self.On3DClick)
		toolbarUtil.RadioButton(self.toolbar, group, 'object-top-on.png', 'object-top-off.png', 'Topdown view', callback=self.OnTopClick).SetValue(True)
		self.toolbar.AddSeparator()
		toolbarUtil.NormalButton(self.toolbar, self.OnQuit, 'exit.png', 'Close project planner')
		self.toolbar.AddSeparator()
		toolbarUtil.NormalButton(self.toolbar, self.OnPreferences, 'preferences.png', 'Project planner preferences')
		
		self.toolbar.Realize()

		self.toolbar2 = toolbarUtil.Toolbar(self.panel)

		toolbarUtil.NormalButton(self.toolbar2, self.OnAddModel, 'object-add.png', 'Add model')
		toolbarUtil.NormalButton(self.toolbar2, self.OnRemModel, 'object-remove.png', 'Remove model')
		self.toolbar2.AddSeparator()
		toolbarUtil.NormalButton(self.toolbar2, self.OnMoveUp, 'move-up.png', 'Move model up in print list')
		toolbarUtil.NormalButton(self.toolbar2, self.OnMoveDown, 'move-down.png', 'Move model down in print list')
		toolbarUtil.NormalButton(self.toolbar2, self.OnCopy, 'copy.png', 'Make a copy of the current selected object')
		toolbarUtil.NormalButton(self.toolbar2, self.OnSetCustomProfile, 'set-profile.png', 'Set a custom profile to be used to slice a specific object.')
		self.toolbar2.AddSeparator()
		toolbarUtil.NormalButton(self.toolbar2, self.OnAutoPlace, 'autoplace.png', 'Automaticly organize the objects on the platform.')
		toolbarUtil.NormalButton(self.toolbar2, self.OnSlice, 'slice.png', 'Slice to project into a gcode file.')
		self.toolbar2.Realize()

		self.toolbar3 = toolbarUtil.Toolbar(self.panel)
		self.mirrorX = toolbarUtil.ToggleButton(self.toolbar3, 'flip_x', 'object-mirror-x-on.png', 'object-mirror-x-off.png', 'Mirror X', callback=self.OnMirrorChange)
		self.mirrorY = toolbarUtil.ToggleButton(self.toolbar3, 'flip_y', 'object-mirror-y-on.png', 'object-mirror-y-off.png', 'Mirror Y', callback=self.OnMirrorChange)
		self.mirrorZ = toolbarUtil.ToggleButton(self.toolbar3, 'flip_z', 'object-mirror-z-on.png', 'object-mirror-z-off.png', 'Mirror Z', callback=self.OnMirrorChange)
		self.toolbar3.AddSeparator()

		# Swap
		self.swapXZ = toolbarUtil.ToggleButton(self.toolbar3, 'swap_xz', 'object-swap-xz-on.png', 'object-swap-xz-off.png', 'Swap XZ', callback=self.OnMirrorChange)
		self.swapYZ = toolbarUtil.ToggleButton(self.toolbar3, 'swap_yz', 'object-swap-yz-on.png', 'object-swap-yz-off.png', 'Swap YZ', callback=self.OnMirrorChange)
		self.toolbar3.Realize()
		
		sizer = wx.GridBagSizer(2,2)
		self.panel.SetSizer(sizer)
		self.preview = PreviewGLCanvas(self.panel, self)
		self.listbox = wx.ListBox(self.panel, -1, choices=[])
		self.addButton = wx.Button(self.panel, -1, "Add")
		self.remButton = wx.Button(self.panel, -1, "Remove")
		self.sliceButton = wx.Button(self.panel, -1, "Slice")
		self.autoPlaceButton = wx.Button(self.panel, -1, "Auto Place")
		
		sizer.Add(self.toolbar, (0,0), span=(1,1), flag=wx.EXPAND|wx.LEFT|wx.RIGHT)
		sizer.Add(self.toolbar2, (0,1), span=(1,2), flag=wx.EXPAND|wx.LEFT|wx.RIGHT)
		sizer.Add(self.preview, (1,0), span=(5,1), flag=wx.EXPAND)
		sizer.Add(self.listbox, (1,1), span=(1,2), flag=wx.EXPAND)
		sizer.Add(self.toolbar3, (2,1), span=(1,2), flag=wx.EXPAND|wx.LEFT|wx.RIGHT)
		sizer.Add(self.addButton, (3,1), span=(1,1))
		sizer.Add(self.remButton, (3,2), span=(1,1))
		sizer.Add(self.sliceButton, (4,1), span=(1,1))
		sizer.Add(self.autoPlaceButton, (4,2), span=(1,1))
		sizer.AddGrowableCol(0)
		sizer.AddGrowableRow(1)
		
		self.addButton.Bind(wx.EVT_BUTTON, self.OnAddModel)
		self.remButton.Bind(wx.EVT_BUTTON, self.OnRemModel)
		self.sliceButton.Bind(wx.EVT_BUTTON, self.OnSlice)
		self.autoPlaceButton.Bind(wx.EVT_BUTTON, self.OnAutoPlace)
		self.listbox.Bind(wx.EVT_LISTBOX, self.OnListSelect)

		panel = wx.Panel(self.panel, -1)
		sizer.Add(panel, (5,1), span=(1,2))
		
		sizer = wx.GridBagSizer(2,2)
		panel.SetSizer(sizer)
		
		self.scaleCtrl = wx.TextCtrl(panel, -1, '')
		self.rotateCtrl = wx.SpinCtrl(panel, -1, '', size=(21*4,21), style=wx.SP_ARROW_KEYS)
		self.rotateCtrl.SetRange(0, 360)

		sizer.Add(wx.StaticText(panel, -1, 'Scale'), (0,0), flag=wx.ALIGN_CENTER_VERTICAL)
		sizer.Add(self.scaleCtrl, (0,1), flag=wx.ALIGN_BOTTOM|wx.EXPAND)
		sizer.Add(wx.StaticText(panel, -1, 'Rotate'), (1,0), flag=wx.ALIGN_CENTER_VERTICAL)
		sizer.Add(self.rotateCtrl, (1,1), flag=wx.ALIGN_BOTTOM|wx.EXPAND)

		if int(profile.getPreference('extruder_amount')) > 1:
			self.extruderCtrl = wx.ComboBox(panel, -1, '1', choices=map(str, range(1, int(profile.getPreference('extruder_amount'))+1)), style=wx.CB_DROPDOWN|wx.CB_READONLY)
			sizer.Add(wx.StaticText(panel, -1, 'Extruder'), (2,0), flag=wx.ALIGN_CENTER_VERTICAL)
			sizer.Add(self.extruderCtrl, (2,1), flag=wx.ALIGN_BOTTOM|wx.EXPAND)
			self.extruderCtrl.Bind(wx.EVT_COMBOBOX, self.OnExtruderChange)

		self.scaleCtrl.Bind(wx.EVT_TEXT, self.OnScaleChange)
		self.rotateCtrl.Bind(wx.EVT_SPINCTRL, self.OnRotateChange)

		self.SetSize((800,600))

	def OnClose(self, e):
		self.Destroy()

	def OnQuit(self, e):
		self.Close()
	
	def OnPreferences(self, e):
		prefDialog = preferencesDialog(self)
		prefDialog.Centre()
		prefDialog.Show(True)
	
	def OnSaveProject(self, e):
		dlg=wx.FileDialog(self, "Save project file", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
		dlg.SetWildcard("Project files (*.curaproject)|*.curaproject")
		if dlg.ShowModal() == wx.ID_OK:
			cp = ConfigParser.ConfigParser()
			i = 0
			for item in self.list:
				section = 'model_%d' % (i)
				cp.add_section(section)
				cp.set(section, 'filename', item.filename.encode("utf-8"))
				cp.set(section, 'centerX', str(item.centerX))
				cp.set(section, 'centerY', str(item.centerY))
				cp.set(section, 'scale', str(item.scale))
				cp.set(section, 'rotate', str(item.rotate))
				cp.set(section, 'flipX', str(item.flipX))
				cp.set(section, 'flipY', str(item.flipY))
				cp.set(section, 'flipZ', str(item.flipZ))
				cp.set(section, 'swapXZ', str(item.swapXZ))
				cp.set(section, 'swapYZ', str(item.swapYZ))
				cp.set(section, 'extruder', str(item.extruder+1))
				if item.profile != None:
					cp.set(section, 'profile', item.profile)
				i += 1
			cp.write(open(dlg.GetPath(), "w"))
		dlg.Destroy()

	def OnLoadProject(self, e):
		dlg=wx.FileDialog(self, "Open project file", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
		dlg.SetWildcard("Project files (*.curaproject)|*.curaproject")
		if dlg.ShowModal() == wx.ID_OK:
			cp = ConfigParser.ConfigParser()
			cp.read(dlg.GetPath())
			self.list = []
			i = 0
			while cp.has_section('model_%d' % (i)):
				section = 'model_%d' % (i)
				
				item = ProjectObject(self, unicode(cp.get(section, 'filename'), "utf-8"))
				item.centerX = float(cp.get(section, 'centerX'))
				item.centerY = float(cp.get(section, 'centerY'))
				item.scale = float(cp.get(section, 'scale'))
				item.rotate = float(cp.get(section, 'rotate'))
				item.flipX = cp.get(section, 'flipX') == 'True'
				item.flipY = cp.get(section, 'flipY') == 'True'
				item.flipZ = cp.get(section, 'flipZ') == 'True'
				item.swapXZ = cp.get(section, 'swapXZ') == 'True'
				item.swapYZ = cp.get(section, 'swapYZ') == 'True'
				if cp.has_option(section, 'extruder'):
					item.extuder = int(cp.get(section, 'extruder')) - 1
				if cp.has_option(section, 'profile'):
					item.profile = cp.get(section, 'profile')
				item.updateModelTransform()
				i += 1
				
				self.list.append(item)

			self.selected = self.list[0]
			self._updateListbox()			
			self.OnListSelect(None)
			self.preview.Refresh()

		dlg.Destroy()

	def On3DClick(self):
		self.preview.yaw = 30
		self.preview.pitch = 60
		self.preview.zoom = 300
		self.preview.view3D = True
		self.preview.Refresh()

	def OnTopClick(self):
		self.preview.view3D = False
		self.preview.zoom = self.machineSize.x / 2 + 10
		self.preview.offsetX = 0
		self.preview.offsetY = 0
		self.preview.Refresh()

	def OnListSelect(self, e):
		if self.listbox.GetSelection() == -1:
			return
		self.selection = self.list[self.listbox.GetSelection()]
		self.scaleCtrl.SetValue(str(self.selection.scale))
		self.rotateCtrl.SetValue(int(self.selection.rotate))
		if int(profile.getPreference('extruder_amount')) > 1:
			self.extruderCtrl.SetValue(str(self.selection.extruder+1))

		self.mirrorX.SetValue(self.selection.flipX)
		self.mirrorY.SetValue(self.selection.flipY)
		self.mirrorZ.SetValue(self.selection.flipZ)
		self.swapXZ.SetValue(self.selection.swapXZ)
		self.swapYZ.SetValue(self.selection.swapYZ)
		
		self.preview.Refresh()
	
	def OnAddModel(self, e):
		dlg=wx.FileDialog(self, "Open file to print", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST|wx.FD_MULTIPLE)
		dlg.SetWildcard("STL files (*.stl)|*.stl;*.STL")
		if dlg.ShowModal() == wx.ID_OK:
			for filename in dlg.GetPaths():
				item = ProjectObject(self, filename)
				profile.putPreference('lastFile', item.filename)
				self.list.append(item)
				self.selection = item
				self._updateListbox()
				self.OnListSelect(None)
		self.preview.Refresh()
		dlg.Destroy()
	
	def OnRemModel(self, e):
		if self.selection == None:
			return
		self.list.remove(self.selection)
		self._updateListbox()
		self.preview.Refresh()
	
	def OnMoveUp(self, e):
		if self.selection == None:
			return
		i = self.listbox.GetSelection()
		if i == 0:
			return
		self.list.remove(self.selection)
		self.list.insert(i-1, self.selection)
		self._updateListbox()
		self.preview.Refresh()

	def OnMoveDown(self, e):
		if self.selection == None:
			return
		i = self.listbox.GetSelection()
		if i == len(self.list) - 1:
			return
		self.list.remove(self.selection)
		self.list.insert(i+1, self.selection)
		self._updateListbox()
		self.preview.Refresh()
	
	def OnCopy(self, e):
		if self.selection == None:
			return
		
		item = self.selection.clone()
		self.list.append(item)
		self.selection = item
		
		self._updateListbox()
		self.preview.Refresh()
	
	def OnSetCustomProfile(self, e):
		if self.selection == None:
			return

		dlg=wx.FileDialog(self, "Select profile", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
		dlg.SetWildcard("Profile files (*.ini)|*.ini;*.INI")
		if dlg.ShowModal() == wx.ID_OK:
			self.selection.profile = dlg.GetPath()
		else:
			self.selection.profile = None
		self._updateListbox()
		dlg.Destroy()
	
	def _updateListbox(self):
		self.listbox.Clear()
		for item in self.list:
			if item.profile != None:
				self.listbox.AppendAndEnsureVisible(os.path.split(item.filename)[1] + " *")
			else:
				self.listbox.AppendAndEnsureVisible(os.path.split(item.filename)[1])
		if self.selection in self.list:
			self.listbox.SetSelection(self.list.index(self.selection))
		elif len(self.list) > 0:
			self.selection = self.list[0]
			self.listbox.SetSelection(0)
		else:
			self.selection = None
			self.listbox.SetSelection(-1)

	def OnAutoPlace(self, e):
		bestAllowedSize = int(self.machineSize.y)
		bestArea = self._doAutoPlace(bestAllowedSize)
		for i in xrange(10, int(self.machineSize.y), 10):
			area = self._doAutoPlace(i)
			if area < bestArea:
				bestAllowedSize = i
				bestArea = area
		self._doAutoPlace(bestAllowedSize)
		for item in self.list:
			item.clampXY()
		self.preview.Refresh()
	
	def _doAutoPlace(self, allowedSizeY):
		extraSizeMin = self.headSizeMin
		extraSizeMax = self.headSizeMax
		if profile.getProfileSettingFloat('skirt_line_count') > 0:
			skirtSize = profile.getProfileSettingFloat('skirt_line_count') * profile.calculateEdgeWidth() + profile.getProfileSettingFloat('skirt_gap')
			extraSizeMin = extraSizeMin + util3d.Vector3(skirtSize, skirtSize, 0)
			extraSizeMax = extraSizeMax + util3d.Vector3(skirtSize, skirtSize, 0)

		if extraSizeMin.x > extraSizeMax.x:
			posX = self.machineSize.x
			dirX = -1
		else:
			posX = 0
			dirX = 1
		posY = 0
		dirY = 1
		
		minX = self.machineSize.x
		minY = self.machineSize.y
		maxX = 0
		maxY = 0
		for item in self.list:
			item.centerX = posX + item.getMaximum().x * item.scale * dirX
			item.centerY = posY + item.getMaximum().y * item.scale * dirY
			if item.centerY + item.getSize().y >= allowedSizeY:
				if dirX < 0:
					posX = minX - extraSizeMax.x - 1
				else:
					posX = maxX + extraSizeMin.x + 1
				posY = 0
				item.centerX = posX + item.getMaximum().x * item.scale * dirX
				item.centerY = posY + item.getMaximum().y * item.scale * dirY
			posY += item.getSize().y  * item.scale * dirY + extraSizeMin.y + 1
			minX = min(minX, item.centerX - item.getSize().x * item.scale / 2)
			minY = min(minY, item.centerY - item.getSize().y * item.scale / 2)
			maxX = max(maxX, item.centerX + item.getSize().x * item.scale / 2)
			maxY = max(maxY, item.centerY + item.getSize().y * item.scale / 2)
		
		for item in self.list:
			if dirX < 0:
				item.centerX -= minX / 2
			else:
				item.centerX += (self.machineSize.x - maxX) / 2
			item.centerY += (self.machineSize.y - maxY) / 2
		
		if minX < 0 or maxX > self.machineSize.x:
			return ((maxX - minX) + (maxY - minY)) * 100
		
		return (maxX - minX) + (maxY - minY)

	def OnSlice(self, e):
		put = profile.setTempOverride
		oldProfile = profile.getGlobalProfileString()

		put('model_multiply_x', '1')
		put('model_multiply_y', '1')
		put('enable_raft', 'False')
		put('add_start_end_gcode', 'False')
		put('gcode_extension', 'project_tmp')
		
		clearZ = 0
		actionList = []
		for item in self.list:
			if item.profile != None and os.path.isfile(item.profile):
				profile.loadGlobalProfile(item.profile)
			put('machine_center_x', item.centerX - self.extruderOffset[item.extruder].x)
			put('machine_center_y', item.centerY - self.extruderOffset[item.extruder].y)
			put('model_scale', item.scale)
			put('flip_x', item.flipX)
			put('flip_y', item.flipY)
			put('flip_z', item.flipZ)
			put('model_rotate_base', item.rotate)
			put('swap_xz', item.swapXZ)
			put('swap_yz', item.swapYZ)
			
			action = Action()
			action.sliceCmd = sliceRun.getSliceCommand(item.filename)
			action.centerX = item.centerX
			action.centerY = item.centerY
			action.extruder = item.extruder
			action.filename = item.filename
			clearZ = max(clearZ, item.getMaximum().z * item.scale + 5.0)
			action.clearZ = clearZ
			action.leaveResultForNextSlice = False
			action.usePreviousSlice = False
			actionList.append(action)

			if self.list.index(item) > 0 and item.isSameExceptForPosition(self.list[self.list.index(item)-1]):
				actionList[-2].leaveResultForNextSlice = True
				actionList[-1].usePreviousSlice = True

			if item.profile != None:
				profile.loadGlobalProfileFromString(oldProfile)
		
		#Restore the old profile.
		profile.resetTempOverride()
		
		dlg=wx.FileDialog(self, "Save project gcode file", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
		dlg.SetWildcard("GCode file (*.gcode)|*.gcode")
		if dlg.ShowModal() != wx.ID_OK:
			dlg.Destroy()
			return
		resultFilename = dlg.GetPath()
		dlg.Destroy()
		
		pspw = ProjectSliceProgressWindow(actionList, resultFilename)
		pspw.extruderOffset = self.extruderOffset
		pspw.Centre()
		pspw.Show(True)
	
	def OnScaleChange(self, e):
		if self.selection == None:
			return
		try:
			self.selection.scale = float(self.scaleCtrl.GetValue())
		except ValueError:
			self.selection.scale = 1.0
		self.preview.Refresh()
	
	def OnRotateChange(self, e):
		if self.selection == None:
			return
		self.selection.rotate = float(self.rotateCtrl.GetValue())
		self.selection.updateModelTransform()
		self.preview.Refresh()

	def OnExtruderChange(self, e):
		if self.selection == None:
			return
		self.selection.extruder = int(self.extruderCtrl.GetValue()) - 1
		self.preview.Refresh()
		
	def OnMirrorChange(self):
		if self.selection == None:
			return
		self.selection.flipX = self.mirrorX.GetValue()
		self.selection.flipY = self.mirrorY.GetValue()
		self.selection.flipZ = self.mirrorZ.GetValue()
		self.selection.swapXZ = self.swapXZ.GetValue()
		self.selection.swapYZ = self.swapYZ.GetValue()
		self.selection.updateModelTransform()
		self.preview.Refresh()

class PreviewGLCanvas(glcanvas.GLCanvas):
	def __init__(self, parent, projectPlannerWindow):
		attribList = (glcanvas.WX_GL_RGBA, glcanvas.WX_GL_DOUBLEBUFFER, glcanvas.WX_GL_DEPTH_SIZE, 24, glcanvas.WX_GL_STENCIL_SIZE, 8)
		glcanvas.GLCanvas.__init__(self, parent, attribList = attribList)
		self.parent = projectPlannerWindow
		self.context = glcanvas.GLContext(self)
		wx.EVT_PAINT(self, self.OnPaint)
		wx.EVT_SIZE(self, self.OnSize)
		wx.EVT_ERASE_BACKGROUND(self, self.OnEraseBackground)
		wx.EVT_LEFT_DOWN(self, self.OnMouseLeftDown)
		wx.EVT_MOTION(self, self.OnMouseMotion)
		wx.EVT_MOUSEWHEEL(self, self.OnMouseWheel)
		self.yaw = 30
		self.pitch = 60
		self.zoom = self.parent.machineSize.x / 2 + 10
		self.offsetX = 0
		self.offsetY = 0
		self.view3D = False
		self.allowDrag = False
	
	def OnMouseLeftDown(self,e):
		self.allowDrag = True
	
	def OnMouseMotion(self,e):
		if self.allowDrag and e.Dragging() and e.LeftIsDown():
			if self.view3D:
				self.yaw += e.GetX() - self.oldX
				self.pitch -= e.GetY() - self.oldY
				if self.pitch > 170:
					self.pitch = 170
				if self.pitch < 10:
					self.pitch = 10
			else:
				item = self.parent.selection
				if item != None:
					item.centerX += float(e.GetX() - self.oldX) * self.zoom / self.GetSize().GetHeight() * 2
					item.centerY -= float(e.GetY() - self.oldY) * self.zoom / self.GetSize().GetHeight() * 2
					item.clampXY()
			self.Refresh()
		else:
			self.allowDrag = False
		if e.Dragging() and e.RightIsDown():
			if self.view3D:
				self.zoom += e.GetY() - self.oldY
				if self.zoom < 1:
					self.zoom = 1
			self.Refresh()
		self.oldX = e.GetX()
		self.oldY = e.GetY()
	
	def OnMouseWheel(self,e):
		if self.view3D:
			self.zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0
			if self.zoom < 1.0:
				self.zoom = 1.0
			self.Refresh()
	
	def OnEraseBackground(self,event):
		#Workaround for windows background redraw flicker.
		pass
	
	def OnSize(self,event):
		self.Refresh()

	def OnPaint(self,event):
		dc = wx.PaintDC(self)
		if not hasOpenGLlibs:
			dc.Clear()
			dc.DrawText("No PyOpenGL installation found.\nNo preview window available.", 10, 10)
			return
		self.SetCurrent(self.context)
		opengl.InitGL(self, self.view3D, self.zoom)
		if self.view3D:
			glTranslate(0,0,-self.zoom)
			glRotate(-self.pitch, 1,0,0)
			glRotate(self.yaw, 0,0,1)
			if False: #self.parent.triangleMesh != None:
				glTranslate(0,0,-self.parent.triangleMesh.getMaximum().z / 2)
		else:
			glScale(1.0/self.zoom, 1.0/self.zoom, 1.0)
			glTranslate(self.offsetX, self.offsetY, 0.0)
		glTranslate(-self.parent.machineSize.x/2, -self.parent.machineSize.y/2, 0)

		self.OnDraw()
		self.SwapBuffers()

	def OnDraw(self):
		machineSize = self.parent.machineSize
		opengl.DrawMachine(machineSize)
		extraSizeMin = self.parent.headSizeMin
		extraSizeMax = self.parent.headSizeMax
		if profile.getProfileSettingFloat('skirt_line_count') > 0:
			skirtSize = profile.getProfileSettingFloat('skirt_line_count') * profile.calculateEdgeWidth() + profile.getProfileSettingFloat('skirt_gap')
			extraSizeMin = extraSizeMin + util3d.Vector3(skirtSize, skirtSize, 0)
			extraSizeMax = extraSizeMax + util3d.Vector3(skirtSize, skirtSize, 0)

		for item in self.parent.list:
			item.validPlacement = True
			item.gotHit = False
		
		for idx1 in xrange(0, len(self.parent.list)):
			item = self.parent.list[idx1]
			iMin1 = item.getMinimum() * item.scale + util3d.Vector3(item.centerX, item.centerY, 0) - extraSizeMin - self.parent.extruderOffset[item.extruder]
			iMax1 = item.getMaximum() * item.scale + util3d.Vector3(item.centerX, item.centerY, 0) + extraSizeMax - self.parent.extruderOffset[item.extruder]
			for idx2 in xrange(0, idx1):
				item2 = self.parent.list[idx2]
				iMin2 = item2.getMinimum() * item2.scale + util3d.Vector3(item2.centerX, item2.centerY, 0)
				iMax2 = item2.getMaximum() * item2.scale + util3d.Vector3(item2.centerX, item2.centerY, 0)
				if item != item2 and iMax1.x >= iMin2.x and iMin1.x <= iMax2.x and iMax1.y >= iMin2.y and iMin1.y <= iMax2.y:
					item.validPlacement = False
					item2.gotHit = True
		
		seenSelected = False
		for item in self.parent.list:
			if item == self.parent.selection:
				seenSelected = True
			if item.modelDisplayList == None:
				item.modelDisplayList = glGenLists(1);
			if item.modelDirty:
				item.modelDirty = False
				modelSize = item.getMaximum() - item.getMinimum()
				glNewList(item.modelDisplayList, GL_COMPILE)
				opengl.DrawSTL(item)
				glEndList()
			
			if item.validPlacement:
				if self.parent.selection == item:
					glLightfv(GL_LIGHT0, GL_DIFFUSE,  [1.0, 0.9, 0.7, 1.0])
					glLightfv(GL_LIGHT0, GL_AMBIENT,  [0.2, 0.3, 0.2, 0.0])
				else:
					glLightfv(GL_LIGHT0, GL_DIFFUSE,  [1.0, 0.8, 0.6, 1.0])
					glLightfv(GL_LIGHT0, GL_AMBIENT,  [0.2, 0.1, 0.1, 0.0])
			else:
				if self.parent.selection == item:
					glLightfv(GL_LIGHT0, GL_DIFFUSE,  [1.0, 0.0, 0.0, 0.0])
					glLightfv(GL_LIGHT0, GL_AMBIENT,  [0.2, 0.0, 0.0, 0.0])
				else:
					glLightfv(GL_LIGHT0, GL_DIFFUSE,  [1.0, 0.0, 0.0, 0.0])
					glLightfv(GL_LIGHT0, GL_AMBIENT,  [0.2, 0.0, 0.0, 0.0])
			glPushMatrix()
			
			glEnable(GL_LIGHTING)
			glTranslate(item.centerX, item.centerY, 0)
			glPushMatrix()
			glEnable(GL_NORMALIZE)
			glScalef(item.scale, item.scale, item.scale)
			glCallList(item.modelDisplayList)
			glPopMatrix()
			
			vMin = item.getMinimum() * item.scale
			vMax = item.getMaximum() * item.scale
			vMinHead = vMin - extraSizeMin - self.parent.extruderOffset[item.extruder]
			vMaxHead = vMax + extraSizeMax - self.parent.extruderOffset[item.extruder]

			glDisable(GL_LIGHTING)

			if self.parent.selection == item:
				if item.gotHit:
					glColor3f(1.0,0.0,0.3)
				else:
					glColor3f(1.0,0.0,1.0)
				opengl.DrawBox(vMin, vMax)
				if item.gotHit:
					glColor3f(1.0,0.3,0.0)
				else:
					glColor3f(1.0,1.0,0.0)
				opengl.DrawBox(vMinHead, vMaxHead)
			elif seenSelected:
				if item.gotHit:
					glColor3f(0.5,0.0,0.1)
				else:
					glColor3f(0.5,0.0,0.5)
				opengl.DrawBox(vMinHead, vMaxHead)
			else:
				if item.gotHit:
					glColor3f(0.7,0.1,0.0)
				else:
					glColor3f(0.7,0.7,0.0)
				opengl.DrawBox(vMin, vMax)
			
			glPopMatrix()
		
		glFlush()

class ProjectSliceProgressWindow(wx.Frame):
	def __init__(self, actionList, resultFilename):
		super(ProjectSliceProgressWindow, self).__init__(None, title='Cura')
		self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))
		
		self.actionList = actionList
		self.resultFilename = resultFilename
		self.abort = False
		self.prevStep = 'start'
		self.totalDoneFactor = 0.0
		self.startTime = time.time()
		self.sliceStartTime = time.time()
		
		self.sizer = wx.GridBagSizer(2, 2) 
		self.statusText = wx.StaticText(self, -1, "Building: %s" % (resultFilename))
		self.progressGauge = wx.Gauge(self, -1)
		self.progressGauge.SetRange(10000)
		self.progressGauge2 = wx.Gauge(self, -1)
		self.progressGauge2.SetRange(len(self.actionList))
		self.abortButton = wx.Button(self, -1, "Abort")
		self.sizer.Add(self.statusText, (0,0), span=(1,4))
		self.sizer.Add(self.progressGauge, (1, 0), span=(1,4), flag=wx.EXPAND)
		self.sizer.Add(self.progressGauge2, (2, 0), span=(1,4), flag=wx.EXPAND)

		self.sizer.Add(self.abortButton, (3,0), span=(1,4), flag=wx.ALIGN_CENTER)
		self.sizer.AddGrowableCol(0)
		self.sizer.AddGrowableRow(0)

		self.Bind(wx.EVT_BUTTON, self.OnAbort, self.abortButton)
		self.SetSizer(self.sizer)
		self.Layout()
		self.Fit()
		
		threading.Thread(target=self.OnRun).start()

	def OnAbort(self, e):
		if self.abort:
			self.Close()
		else:
			self.abort = True
			self.abortButton.SetLabel('Close')

	def SetProgress(self, stepName, layer, maxLayer):
		if self.prevStep != stepName:
			self.totalDoneFactor += sliceRun.sliceStepTimeFactor[self.prevStep]
			newTime = time.time()
			#print "#####" + str(newTime-self.startTime) + " " + self.prevStep + " -> " + stepName
			self.startTime = newTime
			self.prevStep = stepName
		
		progresValue = ((self.totalDoneFactor + sliceRun.sliceStepTimeFactor[stepName] * layer / maxLayer) / sliceRun.totalRunTimeFactor) * 10000
		self.progressGauge.SetValue(int(progresValue))
		self.statusText.SetLabel(stepName + " [" + str(layer) + "/" + str(maxLayer) + "]")
	
	def OnRun(self):
		resultFile = open(self.resultFilename, "w")
		put = profile.setTempOverride
		self.progressLog = []
		for action in self.actionList:
			wx.CallAfter(self.SetTitle, "Building: [%d/%d]"  % (self.actionList.index(action) + 1, len(self.actionList)))
			if not action.usePreviousSlice:
				p = subprocess.Popen(action.sliceCmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
				line = p.stdout.readline()
		
				maxValue = 1
				while(len(line) > 0):
					line = line.rstrip()
					if line[0:9] == "Progress[" and line[-1:] == "]":
						progress = line[9:-1].split(":")
						if len(progress) > 2:
							maxValue = int(progress[2])
						wx.CallAfter(self.SetProgress, progress[0], int(progress[1]), maxValue)
					else:
						print line
						self.progressLog.append(line)
						wx.CallAfter(self.statusText.SetLabel, line)
					if self.abort:
						p.terminate()
						wx.CallAfter(self.statusText.SetLabel, "Aborted by user.")
						resultFile.close()
						return
					line = p.stdout.readline()
				self.returnCode = p.wait()
			
			put('machine_center_x', action.centerX - self.extruderOffset[action.extruder].x)
			put('machine_center_y', action.centerY - self.extruderOffset[action.extruder].y)
			put('clear_z', action.clearZ)
			put('extruder', action.extruder)
			
			if action == self.actionList[0]:
				resultFile.write(';TYPE:CUSTOM\n')
				resultFile.write('T%d\n' % (action.extruder))
				currentExtruder = action.extruder
				resultFile.write(profile.getAlterationFileContents('start.gcode'))
			else:
				#reset the extrusion length, and move to the next object center.
				resultFile.write(';TYPE:CUSTOM\n')
				resultFile.write(profile.getAlterationFileContents('nextobject.gcode'))
			resultFile.write(';PRINTNR:%d\n' % self.actionList.index(action))
			profile.resetTempOverride()
			
			if not action.usePreviousSlice:
				f = open(action.filename[: action.filename.rfind('.')] + "_export.project_tmp", "r")
				data = f.read(4096)
				while data != '':
					resultFile.write(data)
					data = f.read(4096)
				f.close()
				savedCenterX = action.centerX
				savedCenterY = action.centerY
			else:
				f = open(action.filename[: action.filename.rfind('.')] + "_export.project_tmp", "r")
				for line in f:
					if line[0] != ';':
						if 'X' in line:
							line = self._adjustNumberInLine(line, 'X', action.centerX - savedCenterX)
						if 'Y' in line:
							line = self._adjustNumberInLine(line, 'Y', action.centerY - savedCenterY)
					resultFile.write(line)
				f.close()

			if not action.leaveResultForNextSlice:
				os.remove(action.filename[: action.filename.rfind('.')] + "_export.project_tmp")
			
			wx.CallAfter(self.progressGauge.SetValue, 10000)
			self.totalDoneFactor = 0.0
			wx.CallAfter(self.progressGauge2.SetValue, self.actionList.index(action) + 1)
		
		resultFile.write(';TYPE:CUSTOM\n')
		resultFile.write('G1 Z%f F%f\n' % self.actionList[-1].clearZ, profile.getProfileSettingFloat('max_z_speed') * 60)
		resultFile.write(profile.getAlterationFileContents('end.gcode'))
		resultFile.close()
		
		gcode = gcodeInterpreter.gcode()
		gcode.load(self.resultFilename)
		
		self.abort = True
		sliceTime = time.time() - self.sliceStartTime
		status = "Build: %s" % (self.resultFilename)
		status += "\nSlicing took: %02d:%02d" % (sliceTime / 60, sliceTime % 60)
		status += "\nFilament: %.2fm %.2fg" % (gcode.extrusionAmount / 1000, gcode.calculateWeight() * 1000)
		status += "\nPrint time: %02d:%02d" % (int(gcode.totalMoveTimeMinute / 60), int(gcode.totalMoveTimeMinute % 60))
		cost = gcode.calculateCost()
		if cost != False:
			status += "\nCost: %s" % (cost)
		wx.CallAfter(self.statusText.SetLabel, status)
		wx.CallAfter(self.OnSliceDone)
	
	def _adjustNumberInLine(self, line, tag, f):
		m = re.search('^(.*'+tag+')([0-9\.]*)(.*)$', line)
		return m.group(1) + str(float(m.group(2)) + f) + m.group(3) + '\n'
	
	def OnSliceDone(self):
		self.abortButton.Destroy()
		self.closeButton = wx.Button(self, -1, "Close")
		self.printButton = wx.Button(self, -1, "Print")
		self.logButton = wx.Button(self, -1, "Show log")
		self.sizer.Add(self.closeButton, (3,0), span=(1,1))
		self.sizer.Add(self.printButton, (3,1), span=(1,1))
		self.sizer.Add(self.logButton, (3,2), span=(1,1))
		if exporer.hasExporer():
			self.openFileLocationButton = wx.Button(self, -1, "Open file location")
			self.Bind(wx.EVT_BUTTON, self.OnOpenFileLocation, self.openFileLocationButton)
			self.sizer.Add(self.openFileLocationButton, (3,3), span=(1,1))
		self.Bind(wx.EVT_BUTTON, self.OnAbort, self.closeButton)
		self.Bind(wx.EVT_BUTTON, self.OnPrint, self.printButton)
		self.Bind(wx.EVT_BUTTON, self.OnShowLog, self.logButton)
		self.Layout()
		self.Fit()

	def OnOpenFileLocation(self, e):
		exporer.openExporer(self.resultFilename)
	
	def OnPrint(self, e):
		printWindow.printFile(self.resultFilename)

	def OnShowLog(self, e):
		LogWindow('\n'.join(self.progressLog))

class preferencesDialog(configBase.configWindowBase):
	def __init__(self, parent):
		super(preferencesDialog, self).__init__(title="Project Planner Preferences")
		
		self.parent = parent
		wx.EVT_CLOSE(self, self.OnClose)
		
		extruderAmount = int(profile.getPreference('extruder_amount'))
		
		left, right, main = self.CreateConfigPanel(self)
		configBase.TitleRow(left, 'Machine head size')
		c = configBase.SettingRow(left, 'Head size - X towards home (mm)', 'extruder_head_size_min_x', '0', 'Size of your printer head in the X direction, on the Ultimaker your fan is in this direction.', type = 'preference')
		validators.validFloat(c, 0.1)
		c = configBase.SettingRow(left, 'Head size - X towards end (mm)', 'extruder_head_size_max_x', '0', 'Size of your printer head in the X direction.', type = 'preference')
		validators.validFloat(c, 0.1)
		c = configBase.SettingRow(left, 'Head size - Y towards home (mm)', 'extruder_head_size_min_y', '0', 'Size of your printer head in the Y direction.', type = 'preference')
		validators.validFloat(c, 0.1)
		c = configBase.SettingRow(left, 'Head size - Y towards end (mm)', 'extruder_head_size_max_y', '0', 'Size of your printer head in the Y direction.', type = 'preference')
		validators.validFloat(c, 0.1)
		
		self.okButton = wx.Button(left, -1, 'Ok')
		left.GetSizer().Add(self.okButton, (left.GetSizer().GetRows(), 1))
		self.okButton.Bind(wx.EVT_BUTTON, self.OnClose)
		
		self.MakeModal(True)
		main.Fit()
		self.Fit()

	def OnClose(self, e):
		self.parent.headSizeMin = util3d.Vector3(profile.getPreferenceFloat('extruder_head_size_min_x'), profile.getPreferenceFloat('extruder_head_size_min_y'),0)
		self.parent.headSizeMax = util3d.Vector3(profile.getPreferenceFloat('extruder_head_size_max_x'), profile.getPreferenceFloat('extruder_head_size_max_y'),0)

		self.MakeModal(False)
		self.Destroy()

class LogWindow(wx.Frame):
	def __init__(self, logText):
		super(LogWindow, self).__init__(None, title="Slice log")
		self.textBox = wx.TextCtrl(self, -1, logText, style=wx.TE_MULTILINE|wx.TE_DONTWRAP|wx.TE_READONLY)
		self.SetSize((400,300))
		self.Centre()
		self.Show(True)

def main():
	app = wx.App(False)
	projectPlanner().Show(True)
	app.MainLoop()

if __name__ == '__main__':
	main()
