chiark / gitweb /
Update preferences to allow math expressions. Fixed #86
[cura.git] / Cura / gui / projectPlanner.py
1 from __future__ import absolute_import\r
2 import __init__\r
3 \r
4 import wx, os, platform, types, webbrowser, math, subprocess, threading, time\r
5 import ConfigParser\r
6 \r
7 from wx import glcanvas\r
8 import wx\r
9 try:\r
10         import OpenGL\r
11         OpenGL.ERROR_CHECKING = False\r
12         from OpenGL.GLU import *\r
13         from OpenGL.GL import *\r
14         hasOpenGLlibs = True\r
15 except:\r
16         print "Failed to find PyOpenGL: http://pyopengl.sourceforge.net/"\r
17         hasOpenGLlibs = False\r
18 \r
19 from gui import opengl\r
20 from gui import toolbarUtil\r
21 from gui import icon\r
22 from util import profile\r
23 from util import util3d\r
24 from util import stl\r
25 from util import sliceRun\r
26 \r
27 class Action():\r
28         pass\r
29 \r
30 class projectPlanner(wx.Frame):\r
31         "Main user interface window"\r
32         def __init__(self):\r
33                 super(projectPlanner, self).__init__(None, title='Cura - Project Planner')\r
34                 \r
35                 self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))\r
36                 wx.EVT_CLOSE(self, self.OnClose)\r
37                 #self.SetIcon(icon.getMainIcon())\r
38                 \r
39                 self.list = []\r
40                 self.selection = None\r
41 \r
42                 self.machineSize = util3d.Vector3(profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height'))\r
43                 self.headSizeMin = util3d.Vector3(profile.getPreferenceFloat('extruder_head_size_min_x'), profile.getPreferenceFloat('extruder_head_size_min_y'),0)\r
44                 self.headSizeMax = util3d.Vector3(profile.getPreferenceFloat('extruder_head_size_max_x'), profile.getPreferenceFloat('extruder_head_size_max_y'),0)\r
45 \r
46                 self.extruderOffset = [\r
47                         util3d.Vector3(0,0,0),\r
48                         util3d.Vector3(profile.getPreferenceFloat('extruder_offset_x1'), profile.getPreferenceFloat('extruder_offset_y1'), 0),\r
49                         util3d.Vector3(profile.getPreferenceFloat('extruder_offset_x2'), profile.getPreferenceFloat('extruder_offset_y2'), 0),\r
50                         util3d.Vector3(profile.getPreferenceFloat('extruder_offset_x3'), profile.getPreferenceFloat('extruder_offset_y3'), 0)]\r
51 \r
52                 self.toolbar = toolbarUtil.Toolbar(self)\r
53 \r
54                 toolbarUtil.NormalButton(self.toolbar, self.OnLoadProject, 'open.png', 'Open project')\r
55                 toolbarUtil.NormalButton(self.toolbar, self.OnSaveProject, 'save.png', 'Save project')\r
56                 self.toolbar.AddSeparator()\r
57                 group = []\r
58                 toolbarUtil.RadioButton(self.toolbar, group, 'object-3d-on.png', 'object-3d-off.png', '3D view', callback=self.On3DClick)\r
59                 toolbarUtil.RadioButton(self.toolbar, group, 'object-top-on.png', 'object-top-off.png', 'Topdown view', callback=self.OnTopClick).SetValue(True)\r
60                 self.toolbar.AddSeparator()\r
61                 toolbarUtil.NormalButton(self.toolbar, self.OnQuit, 'exit.png', 'Close project planner')\r
62                 \r
63                 self.toolbar.Realize()\r
64                 \r
65                 sizer = wx.GridBagSizer(2,2)\r
66                 self.SetSizer(sizer)\r
67                 self.preview = PreviewGLCanvas(self)\r
68                 self.listbox = wx.ListBox(self, -1, choices=[])\r
69                 self.addButton = wx.Button(self, -1, "Add")\r
70                 self.remButton = wx.Button(self, -1, "Remove")\r
71                 self.sliceButton = wx.Button(self, -1, "Slice")\r
72                 self.autoPlaceButton = wx.Button(self, -1, "Auto Place")\r
73                 \r
74                 sizer.Add(self.toolbar, (0,0), span=(1,3), flag=wx.EXPAND)\r
75                 sizer.Add(self.preview, (1,0), span=(4,1), flag=wx.EXPAND)\r
76                 sizer.Add(self.listbox, (1,1), span=(1,2), flag=wx.EXPAND)\r
77                 sizer.Add(self.addButton, (2,1), span=(1,1))\r
78                 sizer.Add(self.remButton, (2,2), span=(1,1))\r
79                 sizer.Add(self.sliceButton, (3,1), span=(1,1))\r
80                 sizer.Add(self.autoPlaceButton, (3,2), span=(1,1))\r
81                 sizer.AddGrowableCol(0)\r
82                 sizer.AddGrowableRow(1)\r
83                 \r
84                 self.addButton.Bind(wx.EVT_BUTTON, self.OnAddModel)\r
85                 self.remButton.Bind(wx.EVT_BUTTON, self.OnRemModel)\r
86                 self.sliceButton.Bind(wx.EVT_BUTTON, self.OnSlice)\r
87                 self.autoPlaceButton.Bind(wx.EVT_BUTTON, self.OnAutoPlace)\r
88                 self.listbox.Bind(wx.EVT_LISTBOX, self.OnListSelect)\r
89 \r
90                 panel = wx.Panel(self, -1)\r
91                 sizer.Add(panel, (4,1), span=(1,2))\r
92                 \r
93                 sizer = wx.GridBagSizer(2,2)\r
94                 panel.SetSizer(sizer)\r
95                 \r
96                 self.scaleCtrl = wx.TextCtrl(panel, -1, '')\r
97                 self.rotateCtrl = wx.SpinCtrl(panel, -1, '', size=(21*4,21), style=wx.SP_ARROW_KEYS)\r
98                 self.rotateCtrl.SetRange(0, 360)\r
99 \r
100                 sizer.Add(wx.StaticText(panel, -1, 'Scale'), (0,0), flag=wx.ALIGN_CENTER_VERTICAL)\r
101                 sizer.Add(self.scaleCtrl, (0,1), flag=wx.ALIGN_BOTTOM|wx.EXPAND)\r
102                 sizer.Add(wx.StaticText(panel, -1, 'Rotate'), (1,0), flag=wx.ALIGN_CENTER_VERTICAL)\r
103                 sizer.Add(self.rotateCtrl, (1,1), flag=wx.ALIGN_BOTTOM|wx.EXPAND)\r
104                 \r
105                 if int(profile.getPreference('extruder_amount')) > 1:\r
106                         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)\r
107                         sizer.Add(wx.StaticText(panel, -1, 'Extruder'), (2,0), flag=wx.ALIGN_CENTER_VERTICAL)\r
108                         sizer.Add(self.extruderCtrl, (2,1), flag=wx.ALIGN_BOTTOM|wx.EXPAND)\r
109                         self.extruderCtrl.Bind(wx.EVT_COMBOBOX, self.OnExtruderChange)\r
110 \r
111                 self.scaleCtrl.Bind(wx.EVT_TEXT, self.OnScaleChange)\r
112                 self.rotateCtrl.Bind(wx.EVT_SPINCTRL, self.OnRotateChange)\r
113 \r
114                 self.SetSize((800,600))\r
115 \r
116         def OnClose(self, e):\r
117                 self.Destroy()\r
118 \r
119         def OnQuit(self, e):\r
120                 self.Close()\r
121         \r
122         def OnSaveProject(self, e):\r
123                 dlg=wx.FileDialog(self, "Save project file", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)\r
124                 dlg.SetWildcard("Project files (*.curaproject)|*.curaproject")\r
125                 if dlg.ShowModal() == wx.ID_OK:\r
126                         cp = ConfigParser.ConfigParser()\r
127                         i = 0\r
128                         for item in self.list:\r
129                                 section = 'model_%d' % (i)\r
130                                 cp.add_section(section)\r
131                                 cp.set(section, 'filename', item.filename.encode("utf-8"))\r
132                                 cp.set(section, 'centerX', str(item.centerX))\r
133                                 cp.set(section, 'centerY', str(item.centerY))\r
134                                 cp.set(section, 'scale', str(item.scale))\r
135                                 cp.set(section, 'rotate', str(item.rotate))\r
136                                 cp.set(section, 'flipX', str(item.flipX))\r
137                                 cp.set(section, 'flipY', str(item.flipY))\r
138                                 cp.set(section, 'flipZ', str(item.flipZ))\r
139                                 cp.set(section, 'swapXZ', str(item.swapXZ))\r
140                                 cp.set(section, 'swapYZ', str(item.swapYZ))\r
141                                 cp.set(section, 'extruder', str(item.extruder+1))\r
142                                 i += 1\r
143                         cp.write(open(dlg.GetPath(), "w"))\r
144                 dlg.Destroy()\r
145 \r
146         def OnLoadProject(self, e):\r
147                 dlg=wx.FileDialog(self, "Open project file", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)\r
148                 dlg.SetWildcard("Project files (*.curaproject)|*.curaproject")\r
149                 if dlg.ShowModal() == wx.ID_OK:\r
150                         cp = ConfigParser.ConfigParser()\r
151                         cp.read(dlg.GetPath())\r
152                         self.list = []\r
153                         self.listbox.Clear()\r
154                         i = 0\r
155                         while cp.has_section('model_%d' % (i)):\r
156                                 section = 'model_%d' % (i)\r
157                                 \r
158                                 item = stl.stlModel()\r
159                                 item.filename = unicode(cp.get(section, 'filename'), "utf-8")\r
160                                 self.loadModelFile(item)\r
161                                 item.centerX = float(cp.get(section, 'centerX'))\r
162                                 item.centerY = float(cp.get(section, 'centerY'))\r
163                                 item.scale = float(cp.get(section, 'scale'))\r
164                                 item.rotate = float(cp.get(section, 'rotate'))\r
165                                 item.flipX = cp.get(section, 'flipX') == 'True'\r
166                                 item.flipY = cp.get(section, 'flipY') == 'True'\r
167                                 item.flipZ = cp.get(section, 'flipZ') == 'True'\r
168                                 item.swapXZ = cp.get(section, 'swapXZ') == 'True'\r
169                                 item.swapYZ = cp.get(section, 'swapYZ') == 'True'\r
170                                 if cp.has_option(section, 'extruder'):\r
171                                         item.extuder = int(cp.get(section, 'extruder'))-1\r
172                                 i += 1\r
173                                 \r
174                                 self.list.append(item)\r
175                                 self.listbox.AppendAndEnsureVisible(os.path.split(item.filename)[1])\r
176                         \r
177                         self.listbox.SetSelection(len(self.list)-1)\r
178                         self.OnListSelect(None)\r
179 \r
180                 dlg.Destroy()\r
181 \r
182         def On3DClick(self):\r
183                 self.preview.yaw = 30\r
184                 self.preview.pitch = 60\r
185                 self.preview.zoom = 300\r
186                 self.preview.view3D = True\r
187                 self.preview.Refresh()\r
188 \r
189         def OnTopClick(self):\r
190                 self.preview.view3D = False\r
191                 self.preview.zoom = self.machineSize.x / 2 + 10\r
192                 self.preview.offsetX = 0\r
193                 self.preview.offsetY = 0\r
194                 self.preview.Refresh()\r
195 \r
196         def OnListSelect(self, e):\r
197                 if self.listbox.GetSelection() == -1:\r
198                         return\r
199                 self.selection = self.list[self.listbox.GetSelection()]\r
200                 self.scaleCtrl.SetValue(str(self.selection.scale))\r
201                 self.rotateCtrl.SetValue(int(self.selection.rotate))\r
202                 if int(profile.getPreference('extruder_amount')) > 1:\r
203                         self.extruderCtrl.SetValue(str(self.selection.extruder+1))\r
204                 self.preview.Refresh()\r
205         \r
206         def OnAddModel(self, e):\r
207                 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)\r
208                 dlg.SetWildcard("STL files (*.stl)|*.stl;*.STL")\r
209                 if dlg.ShowModal() == wx.ID_OK:\r
210                         for filename in dlg.GetPaths():\r
211                                 item = stl.stlModel()\r
212                                 item.filename=filename\r
213                                 profile.putPreference('lastFile', item.filename)\r
214                                 if not(os.path.exists(item.filename)):\r
215                                         return\r
216                                 self.loadModelFile(item)\r
217                                 self.list.append(item)\r
218                                 self.listbox.AppendAndEnsureVisible(os.path.split(item.filename)[1])\r
219                                 self.listbox.SetSelection(len(self.list)-1)\r
220                                 self.OnListSelect(None)\r
221                 dlg.Destroy()\r
222         \r
223         def OnRemModel(self, e):\r
224                 if self.selection == None:\r
225                         return\r
226                 self.list.remove(self.selection)\r
227                 i = self.listbox.GetSelection()\r
228                 self.listbox.Delete(i)\r
229                 if len(self.list) > i:\r
230                         self.listbox.SetSelection(i)\r
231                 elif len(self.list) > 0:\r
232                         self.listbox.SetSelection(len(self.list) - 1)\r
233                 self.selection = None\r
234                 self.OnListSelect(None)\r
235                 self.preview.Refresh()\r
236         \r
237         def OnAutoPlace(self, e):\r
238                 bestAllowedSize = int(self.machineSize.y)\r
239                 bestArea = self._doAutoPlace(bestAllowedSize)\r
240                 for i in xrange(10, int(self.machineSize.y), 10):\r
241                         area = self._doAutoPlace(i)\r
242                         if area < bestArea:\r
243                                 bestAllowedSize = i\r
244                                 bestArea = area\r
245                 self._doAutoPlace(bestAllowedSize)\r
246                 self.preview.Refresh()\r
247         \r
248         def _doAutoPlace(self, allowedSizeY):\r
249                 extraSizeMin = self.headSizeMin\r
250                 extraSizeMax = self.headSizeMax\r
251                 if profile.getProfileSettingFloat('skirt_line_count') > 0:\r
252                         skirtSize = profile.getProfileSettingFloat('skirt_line_count') * profile.calculateEdgeWidth() + profile.getProfileSettingFloat('skirt_gap')\r
253                         extraSizeMin = extraSizeMin + util3d.Vector3(skirtSize, skirtSize, 0)\r
254                         extraSizeMax = extraSizeMax + util3d.Vector3(skirtSize, skirtSize, 0)\r
255 \r
256                 posX = self.machineSize.x\r
257                 posY = 0\r
258                 minX = self.machineSize.x\r
259                 minY = self.machineSize.y\r
260                 maxX = 0\r
261                 maxY = 0\r
262                 dirX = -1\r
263                 dirY = 1\r
264                 for item in self.list:\r
265                         item.centerX = posX + item.getMaximum().x * item.scale * dirX\r
266                         item.centerY = posY + item.getMaximum().y * item.scale * dirY\r
267                         if item.centerY + item.getSize().y >= allowedSizeY:\r
268                                 posX = minX - extraSizeMax.x - 1\r
269                                 posY = 0\r
270                                 item.centerX = posX + item.getMaximum().x * item.scale * dirX\r
271                                 item.centerY = posY + item.getMaximum().y * item.scale * dirY\r
272                         posY += item.getSize().y  * item.scale * dirY + extraSizeMin.y + 1\r
273                         minX = min(minX, item.centerX - item.getSize().x * item.scale / 2)\r
274                         minY = min(minY, item.centerY - item.getSize().y * item.scale / 2)\r
275                         maxX = max(maxX, item.centerX + item.getSize().x * item.scale / 2)\r
276                         maxY = max(maxY, item.centerY + item.getSize().y * item.scale / 2)\r
277                 \r
278                 for item in self.list:\r
279                         item.centerX -= minX / 2\r
280                         item.centerY += (self.machineSize.y - maxY) / 2\r
281                 \r
282                 if minX < 0:\r
283                         return ((maxX - minX) + (maxY - minY)) * 100\r
284                 \r
285                 return (maxX - minX) + (maxY - minY)\r
286 \r
287         def OnSlice(self, e):\r
288                 oldProfile = profile.getGlobalProfileString()\r
289                 \r
290                 put = profile.putProfileSetting\r
291 \r
292                 put('model_multiply_x', '1')\r
293                 put('model_multiply_y', '1')\r
294                 put('enable_raft', 'False')\r
295                 put('add_start_end_gcode', 'False')\r
296                 put('gcode_extension', 'project_tmp')\r
297                 \r
298                 clearZ = 0\r
299                 actionList = []\r
300                 for item in self.list:\r
301                         put('machine_center_x', item.centerX - self.extruderOffset[item.extruder].x)\r
302                         put('machine_center_y', item.centerY - self.extruderOffset[item.extruder].y)\r
303                         put('model_scale', item.scale)\r
304                         put('flip_x', item.flipX)\r
305                         put('flip_y', item.flipY)\r
306                         put('flip_z', item.flipZ)\r
307                         put('model_rotate_base', item.rotate)\r
308                         put('swap_xz', item.swapXZ)\r
309                         put('swap_yz', item.swapYZ)\r
310                         \r
311                         action = Action()\r
312                         action.sliceCmd = sliceRun.getSliceCommand(item.filename)\r
313                         action.centerX = item.centerX\r
314                         action.centerY = item.centerY\r
315                         action.extruder = item.extruder\r
316                         action.filename = item.filename\r
317                         clearZ = max(clearZ, item.getMaximum().z * item.scale)\r
318                         action.clearZ = clearZ\r
319                         actionList.append(action)\r
320                 \r
321                 #Restore the old profile.\r
322                 profile.loadGlobalProfileFromString(oldProfile)\r
323                 \r
324                 dlg=wx.FileDialog(self, "Save project gcode file", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)\r
325                 dlg.SetWildcard("GCode file (*.gcode)|*.gcode")\r
326                 if dlg.ShowModal() != wx.ID_OK:\r
327                         dlg.Destroy()\r
328                         return\r
329                 resultFilename = dlg.GetPath()\r
330                 dlg.Destroy()\r
331                 \r
332                 pspw = ProjectSliceProgressWindow(actionList, resultFilename)\r
333                 pspw.extruderOffset = self.extruderOffset\r
334                 pspw.Centre()\r
335                 pspw.Show(True)\r
336         \r
337         def loadModelFile(self, item):\r
338                 item.load(item.filename)\r
339                 item.origonalVertexes = list(item.vertexes)\r
340                 for i in xrange(0, len(item.origonalVertexes)):\r
341                         item.origonalVertexes[i] = item.origonalVertexes[i].copy()\r
342                 item.getMinimumZ()\r
343                 \r
344                 item.centerX = -item.getMinimum().x + 5\r
345                 item.centerY = -item.getMinimum().y + 5\r
346                 item.scale = 1.0\r
347                 item.rotate = 0.0\r
348                 item.flipX = False\r
349                 item.flipY = False\r
350                 item.flipZ = False\r
351                 item.swapXZ = False\r
352                 item.swapYZ = False\r
353                 item.extruder = 0\r
354                 \r
355                 item.modelDisplayList = None\r
356                 item.modelDirty = False\r
357                 \r
358                 self.updateModelTransform(item)\r
359 \r
360                 item.centerX = -item.getMinimum().x + 5\r
361                 item.centerY = -item.getMinimum().y + 5\r
362 \r
363         def OnScaleChange(self, e):\r
364                 if self.selection == None:\r
365                         return\r
366                 try:\r
367                         self.selection.scale = float(self.scaleCtrl.GetValue())\r
368                 except ValueError:\r
369                         self.selection.scale = 1.0\r
370                 self.preview.Refresh()\r
371         \r
372         def OnRotateChange(self, e):\r
373                 if self.selection == None:\r
374                         return\r
375                 self.selection.rotate = float(self.rotateCtrl.GetValue())\r
376                 self.updateModelTransform(self.selection)\r
377 \r
378         def OnExtruderChange(self, e):\r
379                 if self.selection == None:\r
380                         return\r
381                 self.selection.extruder = int(self.extruderCtrl.GetValue()) - 1\r
382                 self.preview.Refresh()\r
383 \r
384         def updateModelTransform(self, item):\r
385                 rotate = item.rotate / 180.0 * math.pi\r
386                 scaleX = 1.0\r
387                 scaleY = 1.0\r
388                 scaleZ = 1.0\r
389                 if item.flipX:\r
390                         scaleX = -scaleX\r
391                 if item.flipY:\r
392                         scaleY = -scaleY\r
393                 if item.flipZ:\r
394                         scaleZ = -scaleZ\r
395                 swapXZ = item.swapXZ\r
396                 swapYZ = item.swapYZ\r
397                 mat00 = math.cos(rotate) * scaleX\r
398                 mat01 =-math.sin(rotate) * scaleY\r
399                 mat10 = math.sin(rotate) * scaleX\r
400                 mat11 = math.cos(rotate) * scaleY\r
401                 \r
402                 for i in xrange(0, len(item.origonalVertexes)):\r
403                         x = item.origonalVertexes[i].x\r
404                         y = item.origonalVertexes[i].y\r
405                         z = item.origonalVertexes[i].z\r
406                         if swapXZ:\r
407                                 x, z = z, x\r
408                         if swapYZ:\r
409                                 y, z = z, y\r
410                         item.vertexes[i].x = x * mat00 + y * mat01\r
411                         item.vertexes[i].y = x * mat10 + y * mat11\r
412                         item.vertexes[i].z = z * scaleZ\r
413 \r
414                 for face in item.faces:\r
415                         v1 = face.v[0]\r
416                         v2 = face.v[1]\r
417                         v3 = face.v[2]\r
418                         face.normal = (v2 - v1).cross(v3 - v1)\r
419                         face.normal.normalize()\r
420 \r
421                 self.moveModel(item)\r
422         \r
423         def moveModel(self, item):\r
424                 minZ = item.getMinimumZ()\r
425                 min = item.getMinimum()\r
426                 max = item.getMaximum()\r
427                 for v in item.vertexes:\r
428                         v.z -= minZ\r
429                         v.x -= min.x + (max.x - min.x) / 2\r
430                         v.y -= min.y + (max.y - min.y) / 2\r
431                 item.getMinimumZ()\r
432                 item.modelDirty = True\r
433                 self.preview.Refresh()\r
434 \r
435 class PreviewGLCanvas(glcanvas.GLCanvas):\r
436         def __init__(self, parent):\r
437                 attribList = (glcanvas.WX_GL_RGBA, glcanvas.WX_GL_DOUBLEBUFFER, glcanvas.WX_GL_DEPTH_SIZE, 24, glcanvas.WX_GL_STENCIL_SIZE, 8)\r
438                 glcanvas.GLCanvas.__init__(self, parent, attribList = attribList)\r
439                 self.parent = parent\r
440                 self.context = glcanvas.GLContext(self)\r
441                 wx.EVT_PAINT(self, self.OnPaint)\r
442                 wx.EVT_SIZE(self, self.OnSize)\r
443                 wx.EVT_ERASE_BACKGROUND(self, self.OnEraseBackground)\r
444                 wx.EVT_LEFT_DOWN(self, self.OnMouseLeftDown)\r
445                 wx.EVT_MOTION(self, self.OnMouseMotion)\r
446                 wx.EVT_MOUSEWHEEL(self, self.OnMouseWheel)\r
447                 self.yaw = 30\r
448                 self.pitch = 60\r
449                 self.zoom = self.parent.machineSize.x / 2 + 10\r
450                 self.offsetX = 0\r
451                 self.offsetY = 0\r
452                 self.view3D = False\r
453                 self.allowDrag = False\r
454         \r
455         def OnMouseLeftDown(self,e):\r
456                 self.allowDrag = True\r
457         \r
458         def OnMouseMotion(self,e):\r
459                 if self.allowDrag and e.Dragging() and e.LeftIsDown():\r
460                         if self.view3D:\r
461                                 self.yaw += e.GetX() - self.oldX\r
462                                 self.pitch -= e.GetY() - self.oldY\r
463                                 if self.pitch > 170:\r
464                                         self.pitch = 170\r
465                                 if self.pitch < 10:\r
466                                         self.pitch = 10\r
467                         else:\r
468                                 #self.offsetX += float(e.GetX() - self.oldX) * self.zoom / self.GetSize().GetHeight() * 2\r
469                                 #self.offsetY -= float(e.GetY() - self.oldY) * self.zoom / self.GetSize().GetHeight() * 2\r
470                                 item = self.parent.selection\r
471                                 if item != None:\r
472                                         item.centerX += float(e.GetX() - self.oldX) * self.zoom / self.GetSize().GetHeight() * 2\r
473                                         item.centerY -= float(e.GetY() - self.oldY) * self.zoom / self.GetSize().GetHeight() * 2\r
474                                         if item.centerX < -item.getMinimum().x * item.scale + self.parent.extruderOffset[item.extruder].x:\r
475                                                 item.centerX = -item.getMinimum().x * item.scale + self.parent.extruderOffset[item.extruder].x\r
476                                         if item.centerY < -item.getMinimum().y * item.scale + self.parent.extruderOffset[item.extruder].y:\r
477                                                 item.centerY = -item.getMinimum().y * item.scale + self.parent.extruderOffset[item.extruder].y\r
478                                         if item.centerX > self.parent.machineSize.x + self.parent.extruderOffset[item.extruder].x - item.getMaximum().x * item.scale:\r
479                                                 item.centerX = self.parent.machineSize.x + self.parent.extruderOffset[item.extruder].x - item.getMaximum().x * item.scale\r
480                                         if item.centerY > self.parent.machineSize.y + self.parent.extruderOffset[item.extruder].y - item.getMaximum().y * item.scale:\r
481                                                 item.centerY = self.parent.machineSize.y + self.parent.extruderOffset[item.extruder].y - item.getMaximum().y * item.scale\r
482                         self.Refresh()\r
483                 else:\r
484                         self.allowDrag = False\r
485                 if e.Dragging() and e.RightIsDown():\r
486                         if self.view3D:\r
487                                 self.zoom += e.GetY() - self.oldY\r
488                                 if self.zoom < 1:\r
489                                         self.zoom = 1\r
490                         self.Refresh()\r
491                 self.oldX = e.GetX()\r
492                 self.oldY = e.GetY()\r
493         \r
494         def OnMouseWheel(self,e):\r
495                 if self.view3D:\r
496                         self.zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0\r
497                         if self.zoom < 1.0:\r
498                                 self.zoom = 1.0\r
499                         self.Refresh()\r
500         \r
501         def OnEraseBackground(self,event):\r
502                 #Workaround for windows background redraw flicker.\r
503                 pass\r
504         \r
505         def OnSize(self,event):\r
506                 self.Refresh()\r
507 \r
508         def OnPaint(self,event):\r
509                 dc = wx.PaintDC(self)\r
510                 if not hasOpenGLlibs:\r
511                         dc.Clear()\r
512                         dc.DrawText("No PyOpenGL installation found.\nNo preview window available.", 10, 10)\r
513                         return\r
514                 self.SetCurrent(self.context)\r
515                 opengl.InitGL(self, self.view3D, self.zoom)\r
516                 if self.view3D:\r
517                         glTranslate(0,0,-self.zoom)\r
518                         glRotate(-self.pitch, 1,0,0)\r
519                         glRotate(self.yaw, 0,0,1)\r
520                         if False: #self.parent.triangleMesh != None:\r
521                                 glTranslate(0,0,-self.parent.triangleMesh.getMaximum().z / 2)\r
522                 else:\r
523                         glScale(1.0/self.zoom, 1.0/self.zoom, 1.0)\r
524                         glTranslate(self.offsetX, self.offsetY, 0.0)\r
525                 glTranslate(-self.parent.machineSize.x/2, -self.parent.machineSize.y/2, 0)\r
526 \r
527                 self.OnDraw()\r
528                 self.SwapBuffers()\r
529 \r
530         def OnDraw(self):\r
531                 machineSize = self.parent.machineSize\r
532                 opengl.DrawMachine(machineSize)\r
533                 extraSizeMin = self.parent.headSizeMin\r
534                 extraSizeMax = self.parent.headSizeMax\r
535                 if profile.getProfileSettingFloat('skirt_line_count') > 0:\r
536                         skirtSize = profile.getProfileSettingFloat('skirt_line_count') * profile.calculateEdgeWidth() + profile.getProfileSettingFloat('skirt_gap')\r
537                         extraSizeMin = extraSizeMin + util3d.Vector3(skirtSize, skirtSize, 0)\r
538                         extraSizeMax = extraSizeMax + util3d.Vector3(skirtSize, skirtSize, 0)\r
539 \r
540                 for item in self.parent.list:\r
541                         item.validPlacement = True\r
542                         item.gotHit = False\r
543                 \r
544                 for idx1 in xrange(0, len(self.parent.list)):\r
545                         item = self.parent.list[idx1]\r
546                         iMin1 = item.getMinimum() * item.scale + util3d.Vector3(item.centerX, item.centerY, 0) - extraSizeMin - self.parent.extruderOffset[item.extruder]\r
547                         iMax1 = item.getMaximum() * item.scale + util3d.Vector3(item.centerX, item.centerY, 0) + extraSizeMax - self.parent.extruderOffset[item.extruder]\r
548                         for idx2 in xrange(0, idx1):\r
549                                 item2 = self.parent.list[idx2]\r
550                                 iMin2 = item2.getMinimum() * item2.scale + util3d.Vector3(item2.centerX, item2.centerY, 0)\r
551                                 iMax2 = item2.getMaximum() * item2.scale + util3d.Vector3(item2.centerX, item2.centerY, 0)\r
552                                 if item != item2 and iMax1.x >= iMin2.x and iMin1.x <= iMax2.x and iMax1.y >= iMin2.y and iMin1.y <= iMax2.y:\r
553                                         item.validPlacement = False\r
554                                         item2.gotHit = True\r
555                 \r
556                 seenSelected = False\r
557                 for item in self.parent.list:\r
558                         if item == self.parent.selection:\r
559                                 seenSelected = True\r
560                         if item.modelDisplayList == None:\r
561                                 item.modelDisplayList = glGenLists(1);\r
562                         if item.modelDirty:\r
563                                 item.modelDirty = False\r
564                                 modelSize = item.getMaximum() - item.getMinimum()\r
565                                 glNewList(item.modelDisplayList, GL_COMPILE)\r
566                                 opengl.DrawSTL(item)\r
567                                 glEndList()\r
568                         \r
569                         if item.validPlacement:\r
570                                 if self.parent.selection == item:\r
571                                         glLightfv(GL_LIGHT0, GL_DIFFUSE,  [1.0, 0.9, 0.7, 1.0])\r
572                                         glLightfv(GL_LIGHT0, GL_AMBIENT,  [0.2, 0.3, 0.2, 0.0])\r
573                                 else:\r
574                                         glLightfv(GL_LIGHT0, GL_DIFFUSE,  [1.0, 0.8, 0.6, 1.0])\r
575                                         glLightfv(GL_LIGHT0, GL_AMBIENT,  [0.2, 0.1, 0.1, 0.0])\r
576                         else:\r
577                                 if self.parent.selection == item:\r
578                                         glLightfv(GL_LIGHT0, GL_DIFFUSE,  [1.0, 0.0, 0.0, 0.0])\r
579                                         glLightfv(GL_LIGHT0, GL_AMBIENT,  [0.2, 0.0, 0.0, 0.0])\r
580                                 else:\r
581                                         glLightfv(GL_LIGHT0, GL_DIFFUSE,  [1.0, 0.0, 0.0, 0.0])\r
582                                         glLightfv(GL_LIGHT0, GL_AMBIENT,  [0.2, 0.0, 0.0, 0.0])\r
583                         glPushMatrix()\r
584                         \r
585                         glEnable(GL_LIGHTING)\r
586                         glTranslate(item.centerX, item.centerY, 0)\r
587                         glPushMatrix()\r
588                         glEnable(GL_NORMALIZE)\r
589                         glScalef(item.scale, item.scale, item.scale)\r
590                         glCallList(item.modelDisplayList)\r
591                         glPopMatrix()\r
592                         \r
593                         vMin = item.getMinimum() * item.scale\r
594                         vMax = item.getMaximum() * item.scale\r
595                         vMinHead = vMin - extraSizeMin - self.parent.extruderOffset[item.extruder]\r
596                         vMaxHead = vMax + extraSizeMax - self.parent.extruderOffset[item.extruder]\r
597 \r
598                         glDisable(GL_LIGHTING)\r
599 \r
600                         if self.parent.selection == item:\r
601                                 if item.gotHit:\r
602                                         glColor3f(1.0,0.0,0.3)\r
603                                 else:\r
604                                         glColor3f(1.0,0.0,1.0)\r
605                                 opengl.DrawBox(vMin, vMax)\r
606                                 if item.gotHit:\r
607                                         glColor3f(1.0,0.3,0.0)\r
608                                 else:\r
609                                         glColor3f(1.0,1.0,0.0)\r
610                                 opengl.DrawBox(vMinHead, vMaxHead)\r
611                         elif seenSelected:\r
612                                 if item.gotHit:\r
613                                         glColor3f(0.5,0.0,0.1)\r
614                                 else:\r
615                                         glColor3f(0.5,0.0,0.5)\r
616                                 opengl.DrawBox(vMinHead, vMaxHead)\r
617                         else:\r
618                                 if item.gotHit:\r
619                                         glColor3f(0.7,0.1,0.0)\r
620                                 else:\r
621                                         glColor3f(0.7,0.7,0.0)\r
622                                 opengl.DrawBox(vMin, vMax)\r
623                         \r
624                         glPopMatrix()\r
625                 \r
626                 glFlush()\r
627 \r
628 class ProjectSliceProgressWindow(wx.Frame):\r
629         def __init__(self, actionList, resultFilename):\r
630                 super(ProjectSliceProgressWindow, self).__init__(None, title='Cura')\r
631                 self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))\r
632                 \r
633                 self.actionList = actionList\r
634                 self.resultFilename = resultFilename\r
635                 self.abort = False\r
636                 self.prevStep = 'start'\r
637                 self.totalDoneFactor = 0.0\r
638                 self.startTime = time.time()\r
639                 self.sliceStartTime = time.time()\r
640                 \r
641                 #How long does each step take compared to the others. This is used to make a better scaled progress bar, and guess time left.\r
642                 # TODO: Duplicate with sliceProgressPanel, move to sliceRun.\r
643                 self.sliceStepTimeFactor = {\r
644                         'start': 3.3713991642,\r
645                         'slice': 15.4984838963,\r
646                         'preface': 5.17178297043,\r
647                         'inset': 116.362634182,\r
648                         'fill': 215.702672005,\r
649                         'multiply': 21.9536788464,\r
650                         'speed': 12.759510994,\r
651                         'raft': 31.4580039978,\r
652                         'skirt': 19.3436040878,\r
653                         'skin': 1.0,\r
654                         'joris': 1.0,\r
655                         'comb': 23.7805759907,\r
656                         'cool': 27.148763895,\r
657                         'dimension': 90.4914340973\r
658                 }\r
659                 self.totalRunTimeFactor = 0\r
660                 for v in self.sliceStepTimeFactor.itervalues():\r
661                         self.totalRunTimeFactor += v\r
662                 \r
663                 self.sizer = wx.GridBagSizer(2, 2) \r
664                 self.statusText = wx.StaticText(self, -1, "Building: %s" % (resultFilename))\r
665                 self.progressGauge = wx.Gauge(self, -1)\r
666                 self.progressGauge.SetRange(10000)\r
667                 self.progressGauge2 = wx.Gauge(self, -1)\r
668                 self.progressGauge2.SetRange(len(self.actionList))\r
669                 self.abortButton = wx.Button(self, -1, "Abort")\r
670                 self.sizer.Add(self.statusText, (0,0), flag=wx.ALIGN_CENTER)\r
671                 self.sizer.Add(self.progressGauge, (1, 0), flag=wx.EXPAND)\r
672                 self.sizer.Add(self.progressGauge2, (2, 0), flag=wx.EXPAND)\r
673                 self.sizer.Add(self.abortButton, (3,0), flag=wx.ALIGN_CENTER)\r
674                 self.sizer.AddGrowableCol(0)\r
675                 self.sizer.AddGrowableRow(0)\r
676 \r
677                 self.Bind(wx.EVT_BUTTON, self.OnAbort, self.abortButton)\r
678                 self.SetSizer(self.sizer)\r
679                 self.Layout()\r
680                 self.Fit()\r
681                 \r
682                 threading.Thread(target=self.OnRun).start()\r
683 \r
684         def OnAbort(self, e):\r
685                 if self.abort:\r
686                         self.Close()\r
687                 else:\r
688                         self.abort = True\r
689                         self.abortButton.SetLabel('Close')\r
690 \r
691         def SetProgress(self, stepName, layer, maxLayer):\r
692                 if self.prevStep != stepName:\r
693                         self.totalDoneFactor += self.sliceStepTimeFactor[self.prevStep]\r
694                         newTime = time.time()\r
695                         #print "#####" + str(newTime-self.startTime) + " " + self.prevStep + " -> " + stepName\r
696                         self.startTime = newTime\r
697                         self.prevStep = stepName\r
698                 \r
699                 progresValue = ((self.totalDoneFactor + self.sliceStepTimeFactor[stepName] * layer / maxLayer) / self.totalRunTimeFactor) * 10000\r
700                 self.progressGauge.SetValue(int(progresValue))\r
701                 self.statusText.SetLabel(stepName + " [" + str(layer) + "/" + str(maxLayer) + "]")\r
702         \r
703         def OnRun(self):\r
704                 resultFile = open(self.resultFilename, "w")\r
705                 put = profile.putProfileSetting\r
706                 for action in self.actionList:\r
707                         p = subprocess.Popen(action.sliceCmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)\r
708                         line = p.stdout.readline()\r
709                 \r
710                         maxValue = 1\r
711                         self.progressLog = []\r
712                         while(len(line) > 0):\r
713                                 line = line.rstrip()\r
714                                 if line[0:9] == "Progress[" and line[-1:] == "]":\r
715                                         progress = line[9:-1].split(":")\r
716                                         if len(progress) > 2:\r
717                                                 maxValue = int(progress[2])\r
718                                         wx.CallAfter(self.SetProgress, progress[0], int(progress[1]), maxValue)\r
719                                 else:\r
720                                         print line\r
721                                         self.progressLog.append(line)\r
722                                         wx.CallAfter(self.statusText.SetLabel, line)\r
723                                 if self.abort:\r
724                                         p.terminate()\r
725                                         wx.CallAfter(self.statusText.SetLabel, "Aborted by user.")\r
726                                         return\r
727                                 line = p.stdout.readline()\r
728                         self.returnCode = p.wait()\r
729                         \r
730                         oldProfile = profile.getGlobalProfileString()\r
731                         put('machine_center_x', action.centerX - self.extruderOffset[action.extruder].x)\r
732                         put('machine_center_y', action.centerY - self.extruderOffset[action.extruder].y)\r
733                         put('clear_z', action.clearZ)\r
734                         put('extruder', action.extruder)\r
735                         \r
736                         if action == self.actionList[0]:\r
737                                 resultFile.write(';TYPE:CUSTOM\n')\r
738                                 resultFile.write('T%d\n' % (action.extruder))\r
739                                 currentExtruder = action.extruder\r
740                                 resultFile.write(profile.getAlterationFileContents('start.gcode'))\r
741                         else:\r
742                                 #reset the extrusion length, and move to the next object center.\r
743                                 resultFile.write(';TYPE:CUSTOM\n')\r
744                                 resultFile.write(profile.getAlterationFileContents('nextobject.gcode'))\r
745                         resultFile.write(';PRINTNR:%d\n' % self.actionList.index(action))\r
746                         profile.loadGlobalProfileFromString(oldProfile)\r
747                         \r
748                         f = open(action.filename[: action.filename.rfind('.')] + "_export.project_tmp", "r")\r
749                         data = f.read(4096)\r
750                         while data != '':\r
751                                 resultFile.write(data)\r
752                                 data = f.read(4096)\r
753                         f.close()\r
754                         os.remove(action.filename[: action.filename.rfind('.')] + "_export.project_tmp")\r
755                         \r
756                         wx.CallAfter(self.progressGauge.SetValue, 10000)\r
757                         self.totalDoneFactor = 0.0\r
758                         wx.CallAfter(self.progressGauge2.SetValue, self.actionList.index(action) + 1)\r
759                 \r
760                 resultFile.write(';TYPE:CUSTOM\n')\r
761                 resultFile.write(profile.getAlterationFileContents('end.gcode'))\r
762                 resultFile.close()\r
763                 self.abort = True\r
764                 sliceTime = time.time() - self.sliceStartTime\r
765                 wx.CallAfter(self.statusText.SetLabel, 'Slicing took: %d:%d' % (sliceTime / 60, sliceTime % 60))\r
766                 wx.CallAfter(self.abortButton.SetLabel, 'Close')\r
767 \r
768 def main():\r
769         app = wx.App(False)\r
770         projectPlanner().Show(True)\r
771         app.MainLoop()\r
772 \r
773 if __name__ == '__main__':\r
774         main()\r