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