1 from __future__ import absolute_import
2 from __future__ import division
11 from wx import glcanvas
15 OpenGL.ERROR_CHECKING = False
16 from OpenGL.GLU import *
17 from OpenGL.GL import *
20 print "Failed to find PyOpenGL: http://pyopengl.sourceforge.net/"
23 from Cura.gui.util import opengl
24 from Cura.gui.util import toolbarUtil
26 from Cura.util import profile
27 from Cura.util import gcodeInterpreter
28 from Cura.util import meshLoader
29 from Cura.util import util3d
30 from Cura.util import sliceRun
32 class previewObject():
36 self.displayList = None
39 class toolInfo(object):
40 def __init__(self, parent):
43 def OnMouseMove(self, p0, p1):
46 def OnDragStart(self, p0, p1):
49 def OnDrag(self, p0, p1):
56 glDisable(GL_LIGHTING)
58 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
60 size = self.parent.getObjectSize()
61 radius = self.parent.getObjectBoundaryCircle()
63 glTranslate(0,0,size[2]/2 + 5)
64 glRotate(-self.parent.yaw, 0,0,1)
65 if self.parent.pitch < 80:
66 glTranslate(0, radius + 5,0)
67 elif self.parent.pitch < 100:
68 glTranslate(0, (radius + 5) * (90 - self.parent.pitch) / 10,0)
70 glTranslate(0,-(radius + 5),0)
71 opengl.glDrawStringCenter("%dx%dx%d" % (size[0], size[1], size[2]))
77 glVertex3f(size[0], size[1], size[2])
78 glVertex3f(size[0], size[1], size[2]/4*3)
79 glVertex3f(size[0], size[1], size[2])
80 glVertex3f(size[0], size[1]/4*3, size[2])
81 glVertex3f(size[0], size[1], size[2])
82 glVertex3f(size[0]/4*3, size[1], size[2])
84 glVertex3f(-size[0], -size[1], -size[2])
85 glVertex3f(-size[0], -size[1], -size[2]/4*3)
86 glVertex3f(-size[0], -size[1], -size[2])
87 glVertex3f(-size[0], -size[1]/4*3, -size[2])
88 glVertex3f(-size[0], -size[1], -size[2])
89 glVertex3f(-size[0]/4*3, -size[1], -size[2])
92 class toolRotate(object):
93 def __init__(self, parent):
95 self.rotateRingDist = 1.5
97 self.dragStartAngle = None
98 self.dragEndAngle = None
100 def _ProjectToPlanes(self, p0, p1):
101 pp0 = p0 - [0,0,self.parent.getObjectSize()[2]/2]
102 pp1 = p1 - [0,0,self.parent.getObjectSize()[2]/2]
103 cursorX0 = pp0 - (pp1 - pp0) * (pp0[0] / (pp1[0] - pp0[0]))
104 cursorY0 = pp0 - (pp1 - pp0) * (pp0[1] / (pp1[1] - pp0[1]))
105 cursorZ0 = pp0 - (pp1 - pp0) * (pp0[2] / (pp1[2] - pp0[2]))
106 cursorYZ = math.sqrt((cursorX0[1] * cursorX0[1]) + (cursorX0[2] * cursorX0[2]))
107 cursorXZ = math.sqrt((cursorY0[0] * cursorY0[0]) + (cursorY0[2] * cursorY0[2]))
108 cursorXY = math.sqrt((cursorZ0[0] * cursorZ0[0]) + (cursorZ0[1] * cursorZ0[1]))
109 return cursorX0, cursorY0, cursorZ0, cursorYZ, cursorXZ, cursorXY
111 def OnMouseMove(self, p0, p1):
112 radius = self.parent.getObjectBoundaryCircle()
113 cursorX0, cursorY0, cursorZ0, cursorYZ, cursorXZ, cursorXY = self._ProjectToPlanes(p0, p1)
114 oldDragPlane = self.dragPlane
115 if radius * (self.rotateRingDist - 0.1) <= cursorXY <= radius * (self.rotateRingDist + 0.1) or radius * (self.rotateRingDist - 0.1) <= cursorYZ <= radius * (self.rotateRingDist + 0.1) or radius * (self.rotateRingDist - 0.1) <= cursorXZ <= radius * (self.rotateRingDist + 0.1):
116 self.parent.SetCursor(wx.StockCursor(wx.CURSOR_SIZING))
117 if self.dragStartAngle is None:
118 if radius * (self.rotateRingDist - 0.1) <= cursorXY <= radius * (self.rotateRingDist + 0.1):
119 self.dragPlane = 'XY'
120 elif radius * (self.rotateRingDist - 0.1) <= cursorXZ <= radius * (self.rotateRingDist + 0.1):
121 self.dragPlane = 'XZ'
123 self.dragPlane = 'YZ'
125 if self.dragStartAngle is None:
127 self.parent.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
128 if self.dragPlane != oldDragPlane:
129 self.parent.Refresh()
131 def OnDragStart(self, p0, p1):
132 radius = self.parent.getObjectBoundaryCircle()
133 cursorX0, cursorY0, cursorZ0, cursorYZ, cursorXZ, cursorXY = self._ProjectToPlanes(p0, p1)
134 if radius * (self.rotateRingDist - 0.1) <= cursorXY <= radius * (self.rotateRingDist + 0.1) or radius * (self.rotateRingDist - 0.1) <= cursorYZ <= radius * (self.rotateRingDist + 0.1) or radius * (self.rotateRingDist - 0.1) <= cursorXZ <= radius * (self.rotateRingDist + 0.1):
135 if radius * (self.rotateRingDist - 0.1) <= cursorXY <= radius * (self.rotateRingDist + 0.1):
136 self.dragPlane = 'XY'
137 self.dragStartAngle = math.atan2(cursorZ0[1], cursorZ0[0]) * 180 / math.pi
138 elif radius * (self.rotateRingDist - 0.1) <= cursorXZ <= radius * (self.rotateRingDist + 0.1):
139 self.dragPlane = 'XZ'
140 self.dragStartAngle = math.atan2(cursorY0[2], cursorY0[0]) * 180 / math.pi
142 self.dragPlane = 'YZ'
143 self.dragStartAngle = math.atan2(cursorX0[2], cursorX0[1]) * 180 / math.pi
147 def OnDrag(self, p0, p1):
148 cursorX0, cursorY0, cursorZ0, cursorYZ, cursorXZ, cursorXY = self._ProjectToPlanes(p0, p1)
149 if self.dragPlane == 'XY':
150 angle = math.atan2(cursorZ0[1], cursorZ0[0]) * 180 / math.pi
151 elif self.dragPlane == 'XZ':
152 angle = math.atan2(cursorY0[2], cursorY0[0]) * 180 / math.pi
154 angle = math.atan2(cursorX0[2], cursorX0[1]) * 180 / math.pi
155 diff = angle - self.dragStartAngle
156 if wx.GetKeyState(wx.WXK_SHIFT):
157 diff = round(diff / 1) * 1
159 diff = round(diff / 15) * 15
164 rad = diff / 180.0 * math.pi
165 self.dragEndAngle = self.dragStartAngle + diff
166 if self.dragPlane == 'XY':
167 self.parent.tempMatrix = numpy.matrix([[math.cos(rad), math.sin(rad), 0], [-math.sin(rad), math.cos(rad), 0], [0,0,1]], numpy.float64)
168 elif self.dragPlane == 'XZ':
169 self.parent.tempMatrix = numpy.matrix([[math.cos(rad), 0, math.sin(rad)], [0,1,0], [-math.sin(rad), 0, math.cos(rad)]], numpy.float64)
171 self.parent.tempMatrix = numpy.matrix([[1,0,0], [0, math.cos(rad), math.sin(rad)], [0, -math.sin(rad), math.cos(rad)]], numpy.float64)
174 self.dragStartAngle = None
177 glDisable(GL_LIGHTING)
179 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
180 radius = self.parent.getObjectBoundaryCircle()
181 glScalef(self.rotateRingDist * radius, self.rotateRingDist * radius, self.rotateRingDist * radius)
182 if self.dragPlane == 'XY':
183 glColor4ub(255,64,64,255)
184 if self.dragStartAngle is not None:
186 glRotate(self.dragStartAngle, 0,0,1)
193 glRotate(self.dragEndAngle, 0,0,1)
198 glTranslatef(1.1,0,0)
199 glColor4ub(0,0,0,255)
200 opengl.glDrawStringCenter("%d" % (abs(self.dragEndAngle - self.dragStartAngle)))
201 glColor4ub(255,64,64,255)
204 glColor4ub(128,0,0,255)
205 glBegin(GL_LINE_LOOP)
206 for i in xrange(0, 64):
207 glVertex3f(math.cos(i/32.0*math.pi), math.sin(i/32.0*math.pi),0)
209 if self.dragPlane == 'YZ':
210 glColor4ub(64,255,64,255)
211 if self.dragStartAngle is not None:
213 glRotate(self.dragStartAngle, 1,0,0)
220 glRotate(self.dragEndAngle, 1,0,0)
225 glTranslatef(0,1.1,0)
226 glColor4ub(0,0,0,255)
227 opengl.glDrawStringCenter("%d" % (abs(self.dragEndAngle - self.dragStartAngle)))
228 glColor4ub(64,255,64,255)
231 glColor4ub(0,128,0,255)
232 glBegin(GL_LINE_LOOP)
233 for i in xrange(0, 64):
234 glVertex3f(0, math.cos(i/32.0*math.pi), math.sin(i/32.0*math.pi))
236 if self.dragPlane == 'XZ':
237 glColor4ub(255,255,0,255)
238 if self.dragStartAngle is not None:
240 glRotate(self.dragStartAngle, 0,-1,0)
247 glRotate(self.dragEndAngle, 0,-1,0)
252 glTranslatef(1.1,0,0)
253 glColor4ub(0,0,0,255)
254 opengl.glDrawStringCenter("%d" % (abs(self.dragEndAngle - self.dragStartAngle)))
255 glColor4ub(255,255,0,255)
258 glColor4ub(128,128,0,255)
259 glBegin(GL_LINE_LOOP)
260 for i in xrange(0, 64):
261 glVertex3f(math.cos(i/32.0*math.pi), 0, math.sin(i/32.0*math.pi))
264 class toolScale(object):
265 def __init__(self, parent):
268 def OnMouseMove(self, p0, p1):
271 def OnDragStart(self, p0, p1):
274 def OnDrag(self, p0, p1):
281 glDisable(GL_LIGHTING)
282 size = self.parent.getObjectSize() / 2
283 opengl.DrawBox(-size, size)
285 class previewPanel(wx.Panel):
286 def __init__(self, parent):
287 super(previewPanel, self).__init__(parent,-1)
289 self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DDKSHADOW))
290 self.SetMinSize((440,320))
295 self.objectsMinV = None
296 self.objectsMaxV = None
297 self.objectsBoundaryCircleSize = None
298 self.loadThread = None
299 self.machineSize = util3d.Vector3(profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height'))
300 self.machineCenter = util3d.Vector3(self.machineSize.x / 2, self.machineSize.y / 2, 0)
302 self.glCanvas = PreviewGLCanvas(self)
303 #Create the popup window
304 self.warningPopup = wx.PopupWindow(self, flags=wx.BORDER_SIMPLE)
305 self.warningPopup.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_INFOBK))
306 self.warningPopup.text = wx.StaticText(self.warningPopup, -1, 'Reset scale, rotation and mirror?')
307 self.warningPopup.yesButton = wx.Button(self.warningPopup, -1, 'yes', style=wx.BU_EXACTFIT)
308 self.warningPopup.noButton = wx.Button(self.warningPopup, -1, 'no', style=wx.BU_EXACTFIT)
309 self.warningPopup.sizer = wx.BoxSizer(wx.HORIZONTAL)
310 self.warningPopup.SetSizer(self.warningPopup.sizer)
311 self.warningPopup.sizer.Add(self.warningPopup.text, 1, flag=wx.ALL|wx.ALIGN_CENTER_VERTICAL, border=1)
312 self.warningPopup.sizer.Add(self.warningPopup.yesButton, 0, flag=wx.EXPAND|wx.ALL, border=1)
313 self.warningPopup.sizer.Add(self.warningPopup.noButton, 0, flag=wx.EXPAND|wx.ALL, border=1)
314 self.warningPopup.Fit()
315 self.warningPopup.Layout()
316 self.warningPopup.timer = wx.Timer(self)
317 self.Bind(wx.EVT_TIMER, self.OnHideWarning, self.warningPopup.timer)
319 self.Bind(wx.EVT_BUTTON, self.OnWarningPopup, self.warningPopup.yesButton)
320 self.Bind(wx.EVT_BUTTON, self.OnHideWarning, self.warningPopup.noButton)
321 parent.Bind(wx.EVT_MOVE, self.OnMove)
322 parent.Bind(wx.EVT_SIZE, self.OnMove)
324 self.toolbar = toolbarUtil.Toolbar(self)
327 toolbarUtil.RadioButton(self.toolbar, group, 'object-3d-on.png', 'object-3d-off.png', '3D view', callback=self.On3DClick)
328 toolbarUtil.RadioButton(self.toolbar, group, 'object-top-on.png', 'object-top-off.png', 'Topdown view', callback=self.OnTopClick)
329 self.toolbar.AddSeparator()
331 self.showBorderButton = toolbarUtil.ToggleButton(self.toolbar, '', 'view-border-on.png', 'view-border-off.png', 'Show model borders', callback=self.OnViewChange)
332 self.showSteepOverhang = toolbarUtil.ToggleButton(self.toolbar, '', 'steepOverhang-on.png', 'steepOverhang-off.png', 'Show steep overhang', callback=self.OnViewChange)
333 self.toolbar.AddSeparator()
336 self.normalViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-normal-on.png', 'view-normal-off.png', 'Normal model view', callback=self.OnViewChange)
337 self.transparentViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-transparent-on.png', 'view-transparent-off.png', 'Transparent model view', callback=self.OnViewChange)
338 self.xrayViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-xray-on.png', 'view-xray-off.png', 'X-Ray view', callback=self.OnViewChange)
339 self.gcodeViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-gcode-on.png', 'view-gcode-off.png', 'GCode view', callback=self.OnViewChange)
340 self.mixedViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-mixed-on.png', 'view-mixed-off.png', 'Mixed model/GCode view', callback=self.OnViewChange)
341 self.toolbar.AddSeparator()
343 self.layerSpin = wx.SpinCtrl(self.toolbar, -1, '', size=(21*4,21), style=wx.SP_ARROW_KEYS)
344 self.toolbar.AddControl(self.layerSpin)
345 self.Bind(wx.EVT_SPINCTRL, self.OnLayerNrChange, self.layerSpin)
347 self.toolbar2 = toolbarUtil.Toolbar(self)
350 self.infoToolButton = toolbarUtil.RadioButton(self.toolbar2, group, 'object-rotate.png', 'object-rotate.png', 'Object info', callback=self.OnToolChange)
351 self.rotateToolButton = toolbarUtil.RadioButton(self.toolbar2, group, 'object-rotate.png', 'object-rotate.png', 'Rotate object', callback=self.OnToolChange)
352 self.scaleToolButton = toolbarUtil.RadioButton(self.toolbar2, group, 'object-scale.png', 'object-scale.png', 'Scale object', callback=self.OnToolChange)
353 self.mirrorToolButton = toolbarUtil.RadioButton(self.toolbar2, group, 'object-mirror-x-on.png', 'object-mirror-x-off.png', 'Mirror object', callback=self.OnToolChange)
354 self.toolbar2.AddSeparator()
356 self.mirrorX = toolbarUtil.NormalButton(self.toolbar2, self.OnMirrorX, 'object-mirror-x-on.png', 'Mirror X')
357 self.mirrorY = toolbarUtil.NormalButton(self.toolbar2, self.OnMirrorY, 'object-mirror-y-on.png', 'Mirror Y')
358 self.mirrorZ = toolbarUtil.NormalButton(self.toolbar2, self.OnMirrorZ, 'object-mirror-z-on.png', 'Mirror Z')
359 self.toolbar2.AddSeparator()
362 self.scaleReset = toolbarUtil.NormalButton(self.toolbar2, self.OnScaleReset, 'object-scale.png', 'Reset model scale')
363 self.scale = wx.TextCtrl(self.toolbar2, -1, profile.getProfileSetting('model_scale'), size=(21*2,21))
364 self.toolbar2.AddControl(self.scale)
365 self.scale.Bind(wx.EVT_TEXT, self.OnScale)
366 self.scaleMax = toolbarUtil.NormalButton(self.toolbar2, self.OnScaleMax, 'object-max-size.png', 'Scale object to fit machine size')
368 self.toolbar2.AddSeparator()
371 self.rotateReset = toolbarUtil.NormalButton(self.toolbar2, self.OnRotateReset, 'object-rotate.png', 'Reset model rotation')
372 self.layFlat = toolbarUtil.NormalButton(self.toolbar2, self.OnLayFlat, 'object-rotate.png', 'Lay flat')
374 self.toolbar2.Realize()
377 sizer = wx.BoxSizer(wx.VERTICAL)
378 sizer.Add(self.toolbar, 0, flag=wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, border=1)
379 sizer.Add(self.glCanvas, 1, flag=wx.EXPAND)
380 sizer.Add(self.toolbar2, 0, flag=wx.EXPAND|wx.BOTTOM|wx.LEFT|wx.RIGHT, border=1)
383 self.checkReloadFileTimer = wx.Timer(self)
384 self.Bind(wx.EVT_TIMER, self.OnCheckReloadFile, self.checkReloadFileTimer)
385 self.checkReloadFileTimer.Start(1000)
387 self.matrix = numpy.matrix(numpy.array(profile.getObjectMatrix(), numpy.float64).reshape((3,3,)))
388 self.tool = toolInfo(self.glCanvas)
390 def returnToModelViewAndUpdateModel(self):
391 if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':
392 self.setViewMode('Normal')
393 self.updateModelTransform()
395 def OnToolChange(self):
396 if self.infoToolButton.GetValue():
397 self.tool = toolInfo(self.glCanvas)
398 if self.rotateToolButton.GetValue():
399 self.tool = toolRotate(self.glCanvas)
400 if self.scaleToolButton.GetValue():
401 self.tool = toolScale(self.glCanvas)
402 self.returnToModelViewAndUpdateModel()
404 def OnMirrorX(self, e):
405 self.matrix *= numpy.matrix([[-1,0,0],[0,1,0],[0,0,1]], numpy.float64)
406 self.returnToModelViewAndUpdateModel()
408 def OnMirrorY(self, e):
409 self.matrix *= numpy.matrix([[1,0,0],[0,-1,0],[0,0,1]], numpy.float64)
410 self.returnToModelViewAndUpdateModel()
412 def OnMirrorZ(self, e):
413 self.matrix *= numpy.matrix([[1,0,0],[0,1,0],[0,0,-1]], numpy.float64)
414 self.returnToModelViewAndUpdateModel()
416 def OnMove(self, e = None):
419 x, y = self.glCanvas.ClientToScreenXY(0, 0)
420 sx, sy = self.glCanvas.GetClientSizeTuple()
421 self.warningPopup.SetPosition((x, y+sy-self.warningPopup.GetSize().height))
423 def OnScaleReset(self, e):
424 self.scale.SetValue('1.0')
427 def OnScale(self, e):
429 if self.scale.GetValue() != '':
430 scale = self.scale.GetValue()
431 profile.putProfileSetting('model_scale', scale)
432 if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':
433 self.setViewMode('Normal')
434 self.glCanvas.Refresh()
436 def OnScaleMax(self, e = None, onlyScaleDown = False):
437 if self.objectsMinV is None:
439 vMin = self.objectsMinV
440 vMax = self.objectsMaxV
442 if profile.getProfileSettingFloat('skirt_line_count') > 0:
443 skirtSize = 3 + profile.getProfileSettingFloat('skirt_line_count') * profile.calculateEdgeWidth() + profile.getProfileSettingFloat('skirt_gap')
444 scaleX1 = (self.machineSize.x - self.machineCenter.x - skirtSize) / ((vMax[0] - vMin[0]) / 2)
445 scaleY1 = (self.machineSize.y - self.machineCenter.y - skirtSize) / ((vMax[1] - vMin[1]) / 2)
446 scaleX2 = (self.machineCenter.x - skirtSize) / ((vMax[0] - vMin[0]) / 2)
447 scaleY2 = (self.machineCenter.y - skirtSize) / ((vMax[1] - vMin[1]) / 2)
448 scaleZ = self.machineSize.z / (vMax[2] - vMin[2])
449 scale = min(scaleX1, scaleY1, scaleX2, scaleY2, scaleZ)
450 if scale > 1.0 and onlyScaleDown:
452 self.scale.SetValue(str(scale))
453 profile.putProfileSetting('model_scale', self.scale.GetValue())
454 if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':
455 self.setViewMode('Normal')
456 self.glCanvas.Refresh()
458 def OnRotateReset(self, e):
459 self.matrix = numpy.matrix([[1,0,0],[0,1,0],[0,0,1]], numpy.float64)
460 self.updateModelTransform()
462 def OnLayFlat(self, e):
463 transformedVertexes = (numpy.matrix(self.objectList[0].mesh.vertexes, copy = False) * self.matrix).getA()
464 minZvertex = transformedVertexes[transformedVertexes.argmin(0)[2]]
467 for v in transformedVertexes:
468 diff = v - minZvertex
469 len = math.sqrt(diff[0] * diff[0] + diff[1] * diff[1] + diff[2] * diff[2])
472 dot = (diff[2] / len)
478 rad = -math.atan2(dotV[1], dotV[0])
479 self.matrix *= numpy.matrix([[math.cos(rad), math.sin(rad), 0], [-math.sin(rad), math.cos(rad), 0], [0,0,1]], numpy.float64)
480 rad = -math.asin(dotMin)
481 self.matrix *= numpy.matrix([[math.cos(rad), 0, math.sin(rad)], [0,1,0], [-math.sin(rad), 0, math.cos(rad)]], numpy.float64)
484 transformedVertexes = (numpy.matrix(self.objectList[0].mesh.vertexes, copy = False) * self.matrix).getA()
485 minZvertex = transformedVertexes[transformedVertexes.argmin(0)[2]]
488 for v in transformedVertexes:
489 diff = v - minZvertex
490 len = math.sqrt(diff[1] * diff[1] + diff[2] * diff[2])
493 dot = (diff[2] / len)
500 rad = math.asin(dotMin)
502 rad = -math.asin(dotMin)
503 self.matrix *= numpy.matrix([[1,0,0], [0, math.cos(rad), math.sin(rad)], [0, -math.sin(rad), math.cos(rad)]], numpy.float64)
505 self.updateModelTransform()
508 self.glCanvas.yaw = 30
509 self.glCanvas.pitch = 60
510 self.glCanvas.zoom = 300
511 self.glCanvas.view3D = True
512 self.glCanvas.Refresh()
514 def OnTopClick(self):
515 self.glCanvas.view3D = False
516 self.glCanvas.zoom = 100
517 self.glCanvas.offsetX = 0
518 self.glCanvas.offsetY = 0
519 self.glCanvas.Refresh()
521 def OnLayerNrChange(self, e):
522 self.glCanvas.Refresh()
524 def setViewMode(self, mode):
526 self.normalViewButton.SetValue(True)
528 self.gcodeViewButton.SetValue(True)
529 self.glCanvas.viewMode = mode
530 wx.CallAfter(self.glCanvas.Refresh)
532 def loadModelFiles(self, filelist, showWarning = False):
533 while len(filelist) > len(self.objectList):
534 self.objectList.append(previewObject())
535 for idx in xrange(len(filelist), len(self.objectList)):
536 self.objectList[idx].mesh = None
537 self.objectList[idx].filename = None
538 for idx in xrange(0, len(filelist)):
539 obj = self.objectList[idx]
540 if obj.filename != filelist[idx]:
542 self.gcodeFileTime = None
543 self.logFileTime = None
544 obj.filename = filelist[idx]
546 self.gcodeFilename = sliceRun.getExportFilename(filelist[0])
547 #Do the STL file loading in a background thread so we don't block the UI.
548 if self.loadThread is not None and self.loadThread.isAlive():
549 self.loadThread.join()
550 self.loadThread = threading.Thread(target=self.doFileLoadThread)
551 self.loadThread.daemon = True
552 self.loadThread.start()
555 if (self.matrix - numpy.matrix([[1,0,0],[0,1,0],[0,0,1]], numpy.float64)).any() or len(profile.getPluginConfig()) > 0:
556 self.ShowWarningPopup('Reset scale, rotation, mirror and plugins?', self.OnResetAll)
558 def OnCheckReloadFile(self, e):
559 #Only show the reload popup when the window has focus, because the popup goes over other programs.
560 if self.GetParent().FindFocus() is None:
562 for obj in self.objectList:
563 if obj.filename is not None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:
564 self.checkReloadFileTimer.Stop()
565 self.ShowWarningPopup('File changed, reload?', self.reloadModelFiles)
567 def reloadModelFiles(self, filelist = None):
568 if filelist is not None:
569 #Only load this again if the filename matches the file we have already loaded (for auto loading GCode after slicing)
570 for idx in xrange(0, len(filelist)):
571 if self.objectList[idx].filename != filelist[idx]:
575 for idx in xrange(0, len(self.objectList)):
576 filelist.append(self.objectList[idx].filename)
577 self.loadModelFiles(filelist)
580 def doFileLoadThread(self):
581 for obj in self.objectList:
582 if obj.filename is not None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:
583 obj.fileTime = os.stat(obj.filename).st_mtime
584 mesh = meshLoader.loadMesh(obj.filename)
587 self.updateModelTransform()
588 self.OnScaleMax(None, True)
589 self.glCanvas.zoom = numpy.max(self.objectsSize) * 3.5
591 wx.CallAfter(self.updateToolbar)
592 wx.CallAfter(self.glCanvas.Refresh)
594 if os.path.isfile(self.gcodeFilename) and self.gcodeFileTime != os.stat(self.gcodeFilename).st_mtime:
595 self.gcodeFileTime = os.stat(self.gcodeFilename).st_mtime
596 gcode = gcodeInterpreter.gcode()
597 gcode.progressCallback = self.loadProgress
598 gcode.load(self.gcodeFilename)
599 self.gcodeDirty = False
601 self.gcodeDirty = True
604 for line in open(self.gcodeFilename, "rt"):
605 res = re.search(';Model error\(([a-z ]*)\): \(([0-9\.\-e]*), ([0-9\.\-e]*), ([0-9\.\-e]*)\) \(([0-9\.\-e]*), ([0-9\.\-e]*), ([0-9\.\-e]*)\)', line)
607 v1 = util3d.Vector3(float(res.group(2)), float(res.group(3)), float(res.group(4)))
608 v2 = util3d.Vector3(float(res.group(5)), float(res.group(6)), float(res.group(7)))
609 errorList.append([v1, v2])
610 self.errorList = errorList
612 wx.CallAfter(self.updateToolbar)
613 wx.CallAfter(self.glCanvas.Refresh)
614 elif not os.path.isfile(self.gcodeFilename):
616 wx.CallAfter(self.checkReloadFileTimer.Start, 1000)
618 def loadProgress(self, progress):
621 def OnResetAll(self, e = None):
622 profile.putProfileSetting('model_matrix', '1,0,0,0,1,0,0,0,1')
623 profile.setPluginConfig([])
624 self.GetParent().updateProfileToControls()
626 def ShowWarningPopup(self, text, callback = None):
627 self.warningPopup.text.SetLabel(text)
628 self.warningPopup.callback = callback
630 self.warningPopup.yesButton.Show(False)
631 self.warningPopup.noButton.SetLabel('ok')
633 self.warningPopup.yesButton.Show(True)
634 self.warningPopup.noButton.SetLabel('no')
635 self.warningPopup.Fit()
636 self.warningPopup.Layout()
638 self.warningPopup.Show(True)
639 self.warningPopup.timer.Start(5000)
641 def OnWarningPopup(self, e):
642 self.warningPopup.Show(False)
643 self.warningPopup.timer.Stop()
644 self.warningPopup.callback()
646 def OnHideWarning(self, e):
647 self.warningPopup.Show(False)
648 self.warningPopup.timer.Stop()
650 def updateToolbar(self):
651 self.gcodeViewButton.Show(self.gcode != None)
652 self.mixedViewButton.Show(self.gcode != None)
653 self.layerSpin.Show(self.glCanvas.viewMode == "GCode" or self.glCanvas.viewMode == "Mixed")
654 if self.gcode != None:
655 self.layerSpin.SetRange(1, len(self.gcode.layerList) - 1)
656 self.toolbar.Realize()
659 def OnViewChange(self):
660 if self.normalViewButton.GetValue():
661 self.glCanvas.viewMode = "Normal"
662 elif self.transparentViewButton.GetValue():
663 self.glCanvas.viewMode = "Transparent"
664 elif self.xrayViewButton.GetValue():
665 self.glCanvas.viewMode = "X-Ray"
666 elif self.gcodeViewButton.GetValue():
667 self.glCanvas.viewMode = "GCode"
668 elif self.mixedViewButton.GetValue():
669 self.glCanvas.viewMode = "Mixed"
670 self.glCanvas.drawBorders = self.showBorderButton.GetValue()
671 self.glCanvas.drawSteepOverhang = self.showSteepOverhang.GetValue()
673 self.glCanvas.Refresh()
675 def updateModelTransform(self, f=0):
676 if len(self.objectList) < 1 or self.objectList[0].mesh is None:
679 profile.putProfileSetting('model_matrix', ','.join(map(str, list(self.matrix.getA().flatten()))))
680 for obj in self.objectList:
683 obj.mesh.matrix = self.matrix
684 obj.mesh.processMatrix()
686 minV = self.objectList[0].mesh.getMinimum()
687 maxV = self.objectList[0].mesh.getMaximum()
688 objectsBoundaryCircleSize = self.objectList[0].mesh.bounderyCircleSize
689 for obj in self.objectList:
693 minV = numpy.minimum(minV, obj.mesh.getMinimum())
694 maxV = numpy.maximum(maxV, obj.mesh.getMaximum())
695 objectsBoundaryCircleSize = max(objectsBoundaryCircleSize, obj.mesh.bounderyCircleSize)
697 self.objectsMaxV = maxV
698 self.objectsMinV = minV
699 self.objectsSize = self.objectsMaxV - self.objectsMinV
700 self.objectsBoundaryCircleSize = objectsBoundaryCircleSize
702 self.glCanvas.Refresh()
704 def updateProfileToControls(self):
705 self.matrix = numpy.matrix(numpy.array(profile.getObjectMatrix(), numpy.float64).reshape((3,3,)))
706 self.updateModelTransform()
707 self.glCanvas.updateProfileToControls()
709 class PreviewGLCanvas(glcanvas.GLCanvas):
710 def __init__(self, parent):
711 attribList = (glcanvas.WX_GL_RGBA, glcanvas.WX_GL_DOUBLEBUFFER, glcanvas.WX_GL_DEPTH_SIZE, 24, glcanvas.WX_GL_STENCIL_SIZE, 8)
712 glcanvas.GLCanvas.__init__(self, parent, attribList = attribList)
714 self.context = glcanvas.GLContext(self)
715 wx.EVT_PAINT(self, self.OnPaint)
716 wx.EVT_SIZE(self, self.OnSize)
717 wx.EVT_ERASE_BACKGROUND(self, self.OnEraseBackground)
718 wx.EVT_MOTION(self, self.OnMouseMotion)
719 wx.EVT_MOUSEWHEEL(self, self.OnMouseWheel)
726 self.gcodeDisplayList = None
727 self.gcodeDisplayListMade = None
728 self.gcodeDisplayListCount = 0
729 self.objColor = [[1.0, 0.8, 0.6, 1.0], [0.2, 1.0, 0.1, 1.0], [1.0, 0.2, 0.1, 1.0], [0.1, 0.2, 1.0, 1.0]]
733 self.tempMatrix = None
736 def updateProfileToControls(self):
737 self.objColor[0] = profile.getPreferenceColour('model_colour')
738 self.objColor[1] = profile.getPreferenceColour('model_colour2')
739 self.objColor[2] = profile.getPreferenceColour('model_colour3')
740 self.objColor[3] = profile.getPreferenceColour('model_colour4')
742 def OnMouseMotion(self,e):
743 if self.parent.objectsMaxV is not None and self.viewport is not None and self.viewMode != 'GCode' and self.viewMode != 'Mixed':
744 p0 = numpy.array(gluUnProject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 0, self.modelMatrix, self.projMatrix, self.viewport))
745 p1 = numpy.array(gluUnProject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 1, self.modelMatrix, self.projMatrix, self.viewport))
746 self.parent.tool.OnMouseMove(p0, p1)
748 if e.Dragging() and e.LeftIsDown():
749 if self.dragType == '':
750 #Define the drag type depending on the cursor position.
751 self.dragType = 'viewRotate'
752 if self.viewMode != 'GCode' and self.viewMode != 'Mixed':
753 if self.parent.tool.OnDragStart(p0, p1):
754 self.dragType = 'tool'
756 if self.dragType == 'viewRotate':
758 self.yaw += e.GetX() - self.oldX
759 self.pitch -= e.GetY() - self.oldY
765 self.offsetX += float(e.GetX() - self.oldX) * self.zoom / self.GetSize().GetHeight() * 2
766 self.offsetY -= float(e.GetY() - self.oldY) * self.zoom / self.GetSize().GetHeight() * 2
767 elif self.dragType == 'tool':
768 self.parent.tool.OnDrag(p0, p1)
770 #Workaround for buggy ATI cards.
771 size = self.GetSizeTuple()
772 self.SetSize((size[0]+1, size[1]))
773 self.SetSize((size[0], size[1]))
776 if self.dragType != '':
777 if self.tempMatrix is not None:
778 self.parent.matrix *= self.tempMatrix
779 self.parent.updateModelTransform()
780 self.tempMatrix = None
781 self.parent.tool.OnDragEnd()
785 if e.Dragging() and e.RightIsDown():
786 self.zoom += e.GetY() - self.oldY
797 def getObjectBoundaryCircle(self):
798 return self.parent.objectsBoundaryCircleSize
800 def getObjectSize(self):
801 return self.parent.objectsSize
803 def OnMouseWheel(self,e):
804 self.zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0
811 def OnEraseBackground(self,event):
812 #Workaround for windows background redraw flicker.
819 dc = wx.PaintDC(self)
820 if not hasOpenGLlibs:
822 dc.DrawText("No PyOpenGL installation found.\nNo preview window available.", 10, 10)
824 self.SetCurrent(self.context)
825 opengl.InitGL(self, self.view3D, self.zoom)
827 glTranslate(0,0,-self.zoom)
828 glRotate(-self.pitch, 1,0,0)
829 glRotate(self.yaw, 0,0,1)
830 if self.viewMode == "GCode" or self.viewMode == "Mixed":
831 if self.parent.gcode is not None and len(self.parent.gcode.layerList) > self.parent.layerSpin.GetValue() and len(self.parent.gcode.layerList[self.parent.layerSpin.GetValue()]) > 0:
832 glTranslate(0,0,-self.parent.gcode.layerList[self.parent.layerSpin.GetValue()][0].list[-1].z)
834 if self.parent.objectsMaxV is not None:
835 glTranslate(0,0,-self.parent.objectsSize[2] / 2)
837 glTranslate(self.offsetX, self.offsetY, 0)
839 self.viewport = glGetIntegerv(GL_VIEWPORT)
840 self.modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
841 self.projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
843 glTranslate(-self.parent.machineCenter.x, -self.parent.machineCenter.y, 0)
849 machineSize = self.parent.machineSize
851 if self.parent.gcode is not None and self.parent.gcodeDirty:
852 if self.gcodeDisplayListCount < len(self.parent.gcode.layerList) or self.gcodeDisplayList == None:
853 if self.gcodeDisplayList is not None:
854 glDeleteLists(self.gcodeDisplayList, self.gcodeDisplayListCount)
855 self.gcodeDisplayList = glGenLists(len(self.parent.gcode.layerList))
856 self.gcodeDisplayListCount = len(self.parent.gcode.layerList)
857 self.parent.gcodeDirty = False
858 self.gcodeDisplayListMade = 0
860 if self.parent.gcode is not None and self.gcodeDisplayListMade < len(self.parent.gcode.layerList):
861 glNewList(self.gcodeDisplayList + self.gcodeDisplayListMade, GL_COMPILE)
862 opengl.DrawGCodeLayer(self.parent.gcode.layerList[self.gcodeDisplayListMade])
864 self.gcodeDisplayListMade += 1
865 wx.CallAfter(self.Refresh)
868 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
869 for obj in self.parent.objectList:
872 if obj.displayList == None:
873 obj.displayList = glGenLists(1)
874 obj.steepDisplayList = glGenLists(1)
877 glNewList(obj.displayList, GL_COMPILE)
878 opengl.DrawMesh(obj.mesh)
880 glNewList(obj.steepDisplayList, GL_COMPILE)
881 opengl.DrawMeshSteep(obj.mesh, 60)
884 if self.viewMode == "Mixed":
886 glColor3f(0.0,0.0,0.0)
888 glColor3f(1.0,1.0,1.0)
889 glClear(GL_DEPTH_BUFFER_BIT)
893 if self.parent.gcode is not None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):
895 if profile.getPreference('machine_center_is_zero') == 'True':
896 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
897 glEnable(GL_COLOR_MATERIAL)
898 glEnable(GL_LIGHTING)
899 drawUpToLayer = min(self.gcodeDisplayListMade, self.parent.layerSpin.GetValue() + 1)
900 starttime = time.time()
901 for i in xrange(drawUpToLayer - 1, -1, -1):
903 if i < self.parent.layerSpin.GetValue():
904 c = 0.9 - (drawUpToLayer - i) * 0.1
909 glLightfv(GL_LIGHT0, GL_DIFFUSE, [0,0,0,0])
910 glLightfv(GL_LIGHT0, GL_AMBIENT, [c,c,c,c])
911 glCallList(self.gcodeDisplayList + i)
912 if time.time() - starttime > 0.1:
915 glDisable(GL_LIGHTING)
916 glDisable(GL_COLOR_MATERIAL)
917 glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, [0.2, 0.2, 0.2, 1.0]);
918 glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, [0.8, 0.8, 0.8, 1.0]);
921 glColor3f(1.0,1.0,1.0)
923 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
924 for obj in self.parent.objectList:
928 if self.viewMode == "Transparent" or self.viewMode == "Mixed":
929 glLightfv(GL_LIGHT0, GL_DIFFUSE, map(lambda x: x / 2, self.objColor[self.parent.objectList.index(obj)]))
930 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x / 10, self.objColor[self.parent.objectList.index(obj)]))
931 #If we want transparent, then first render a solid black model to remove the printer size lines.
932 if self.viewMode != "Mixed":
934 glColor3f(0.0,0.0,0.0)
936 glColor3f(1.0,1.0,1.0)
937 #After the black model is rendered, render the model again but now with lighting and no depth testing.
938 glDisable(GL_DEPTH_TEST)
939 glEnable(GL_LIGHTING)
941 glBlendFunc(GL_ONE, GL_ONE)
942 glEnable(GL_LIGHTING)
944 glEnable(GL_DEPTH_TEST)
945 elif self.viewMode == "X-Ray":
946 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
947 glDisable(GL_LIGHTING)
948 glDisable(GL_DEPTH_TEST)
949 glEnable(GL_STENCIL_TEST)
950 glStencilFunc(GL_ALWAYS, 1, 1)
951 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
953 glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP);
955 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
956 glStencilFunc(GL_EQUAL, 0, 1)
959 glStencilFunc(GL_EQUAL, 1, 1)
965 for i in xrange(2, 15, 2):
966 glStencilFunc(GL_EQUAL, i, 0xFF);
967 glColor(float(i)/10, float(i)/10, float(i)/5)
969 glVertex3f(-1000,-1000,-1)
970 glVertex3f( 1000,-1000,-1)
971 glVertex3f( 1000, 1000,-1)
972 glVertex3f(-1000, 1000,-1)
974 for i in xrange(1, 15, 2):
975 glStencilFunc(GL_EQUAL, i, 0xFF);
976 glColor(float(i)/10, 0, 0)
978 glVertex3f(-1000,-1000,-1)
979 glVertex3f( 1000,-1000,-1)
980 glVertex3f( 1000, 1000,-1)
981 glVertex3f(-1000, 1000,-1)
985 glDisable(GL_STENCIL_TEST)
986 glEnable(GL_DEPTH_TEST)
988 #Fix the depth buffer
989 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
991 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
992 elif self.viewMode == "Normal":
993 glLightfv(GL_LIGHT0, GL_DIFFUSE, self.objColor[self.parent.objectList.index(obj)])
994 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x * 0.4, self.objColor[self.parent.objectList.index(obj)]))
995 glEnable(GL_LIGHTING)
998 if self.drawBorders and (self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray"):
999 glEnable(GL_DEPTH_TEST)
1000 glDisable(GL_LIGHTING)
1003 modelScale = profile.getProfileSettingFloat('model_scale')
1004 glScalef(modelScale, modelScale, modelScale)
1005 opengl.DrawMeshOutline(obj.mesh)
1008 if self.drawSteepOverhang:
1009 glDisable(GL_LIGHTING)
1012 modelScale = profile.getProfileSettingFloat('model_scale')
1013 glScalef(modelScale, modelScale, modelScale)
1014 glCallList(obj.steepDisplayList)
1018 #if self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray":
1019 # glDisable(GL_LIGHTING)
1020 # glDisable(GL_DEPTH_TEST)
1021 # glDisable(GL_BLEND)
1024 # for err in self.parent.errorList:
1025 # glVertex3f(err[0].x, err[0].y, err[0].z)
1026 # glVertex3f(err[1].x, err[1].y, err[1].z)
1028 # glEnable(GL_DEPTH_TEST)
1030 #Draw the current selected tool
1031 if self.parent.objectsMaxV is not None and self.viewMode != 'GCode' and self.viewMode != 'Mixed':
1033 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, self.parent.objectsSize[2]/2)
1034 self.parent.tool.OnDraw()
1037 opengl.DrawMachine(machineSize)
1041 def convert3x3MatrixTo4x4(self, matrix):
1042 return list(matrix.getA()[0]) + [0] + list(matrix.getA()[1]) + [0] + list(matrix.getA()[2]) + [0, 0,0,0,1]
1044 def drawModel(self, obj):
1045 vMin = self.parent.objectsMinV
1046 vMax = self.parent.objectsMaxV
1047 offset = - vMin - (vMax - vMin) / 2
1049 matrix = self.convert3x3MatrixTo4x4(obj.mesh.matrix)
1052 glTranslate(0, 0, self.parent.objectsSize[2]/2)
1053 if self.tempMatrix is not None:
1054 tempMatrix = self.convert3x3MatrixTo4x4(self.tempMatrix)
1055 glMultMatrixf(tempMatrix)
1056 glTranslate(0, 0, -self.parent.objectsSize[2]/2)
1057 glTranslate(offset[0], offset[1], -vMin[2])
1058 glMultMatrixf(matrix)
1059 glCallList(obj.displayList)