From: daid303 Date: Fri, 26 Apr 2013 08:45:23 +0000 (+0200) Subject: Added saving as STL. Still need to add AMF. X-Git-Tag: 13.05~45 X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=commitdiff_plain;h=e9d34e47c07e19654f20fe57f1650633f0624d9a;p=cura.git Added saving as STL. Still need to add AMF. --- diff --git a/Cura/gui/mainWindow.py b/Cura/gui/mainWindow.py index f2790865..a32ee365 100644 --- a/Cura/gui/mainWindow.py +++ b/Cura/gui/mainWindow.py @@ -29,7 +29,7 @@ class mainWindow(wx.Frame): wx.EVT_CLOSE(self, self.OnClose) - self.SetDropTarget(dropTarget.FileDropTarget(self.OnDropFiles, meshLoader.supportedExtensions())) + self.SetDropTarget(dropTarget.FileDropTarget(self.OnDropFiles, meshLoader.loadSupportedExtensions())) self.normalModeOnlyItems = [] @@ -51,9 +51,13 @@ class mainWindow(wx.Frame): self.menubar = wx.MenuBar() self.fileMenu = wx.Menu() i = self.fileMenu.Append(-1, 'Load model file...\tCTRL+L') - self.Bind(wx.EVT_MENU, lambda e: self.scene.ShowLoadModel(1), i) + self.Bind(wx.EVT_MENU, lambda e: self.scene.showLoadModel(), i) + i = self.fileMenu.Append(-1, 'Save model...\tCTRL+S') + self.Bind(wx.EVT_MENU, lambda e: self.scene.showSaveModel(), i) + + self.fileMenu.AppendSeparator() i = self.fileMenu.Append(-1, 'Print...\tCTRL+P') - self.Bind(wx.EVT_MENU, lambda e: self.scene.ShowPrintWindow(), i) + self.Bind(wx.EVT_MENU, lambda e: self.scene.showPrintWindow(), i) i = self.fileMenu.Append(-1, 'Save GCode...') self.Bind(wx.EVT_MENU, lambda e: self.scene.showSaveGCode(), i) diff --git a/Cura/gui/preview3d.py b/Cura/gui/preview3d.py index e5ac2cad..4a25ea31 100644 --- a/Cura/gui/preview3d.py +++ b/Cura/gui/preview3d.py @@ -343,7 +343,7 @@ class previewPanel(wx.Panel): if wx.TheClipboard.GetData(data): data = data.GetText() if re.match('^http://.*/.*$', data): - if data.endswith(tuple(meshLoader.supportedExtensions())): + if data.endswith(tuple(meshLoader.loadSupportedExtensions())): #Got an url on the clipboard with a model file. pass wx.TheClipboard.Close() diff --git a/Cura/gui/projectPlanner.py b/Cura/gui/projectPlanner.py index 190e1319..626e006d 100644 --- a/Cura/gui/projectPlanner.py +++ b/Cura/gui/projectPlanner.py @@ -122,7 +122,7 @@ class projectPlanner(wx.Frame): self.SetSizer(wx.BoxSizer(wx.VERTICAL)) self.GetSizer().Add(self.panel, 1, flag=wx.EXPAND) - self.SetDropTarget(dropTarget.FileDropTarget(self.OnDropFiles, meshLoader.supportedExtensions())) + self.SetDropTarget(dropTarget.FileDropTarget(self.OnDropFiles, meshLoader.loadSupportedExtensions())) self.list = [] self.selection = None diff --git a/Cura/gui/sceneView.py b/Cura/gui/sceneView.py index 0d6cb3e7..ccbfd033 100644 --- a/Cura/gui/sceneView.py +++ b/Cura/gui/sceneView.py @@ -56,8 +56,8 @@ class SceneView(openglGui.glGuiPanel): self._projMatrix = None self.tempMatrix = None - self.openFileButton = openglGui.glButton(self, 4, 'Load', (0,0), self.ShowLoadModel) - self.printButton = openglGui.glButton(self, 6, 'Print', (1,0), self.ShowPrintWindow) + self.openFileButton = openglGui.glButton(self, 4, 'Load', (0,0), self.showLoadModel) + self.printButton = openglGui.glButton(self, 6, 'Print', (1,0), self.showPrintWindow) self.printButton.setDisabled(True) group = [] @@ -111,10 +111,10 @@ class SceneView(openglGui.glGuiPanel): self.updateToolButtons() self.updateProfileToControls() - def ShowLoadModel(self, button): + def showLoadModel(self, button = 1): if button == 1: dlg=wx.FileDialog(self, 'Open 3D model', os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST) - dlg.SetWildcard(meshLoader.wildcardFilter()) + dlg.SetWildcard(meshLoader.loadWildcardFilter()) if dlg.ShowModal() != wx.ID_OK: dlg.Destroy() return @@ -126,7 +126,19 @@ class SceneView(openglGui.glGuiPanel): self.GetParent().GetParent().GetParent().addToModelMRU(filename) self.loadScene([filename]) - def ShowPrintWindow(self, button): + def showSaveModel(self): + if len(self._scene.objects()) < 1: + return + dlg=wx.FileDialog(self, 'Save 3D model', os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT) + dlg.SetWildcard(meshLoader.saveWildcardFilter()) + if dlg.ShowModal() != wx.ID_OK: + dlg.Destroy() + return + filename = dlg.GetPath() + dlg.Destroy() + meshLoader.saveMeshes(filename, self._scene.objects()) + + def showPrintWindow(self, button): if button == 1: if machineCom.machineIsConnected(): printWindow.printFile(self._slicer.getGCodeFilename()) @@ -238,6 +250,7 @@ class SceneView(openglGui.glGuiPanel): self._selectedObj.resetRotation() self._scene.pushFree() self._selectObject(self._selectedObj) + self.sceneUpdated() def OnLayFlat(self, button): if self._selectedObj is None: @@ -245,11 +258,15 @@ class SceneView(openglGui.glGuiPanel): self._selectedObj.layFlat() self._scene.pushFree() self._selectObject(self._selectedObj) + self.sceneUpdated() def OnScaleReset(self, button): if self._selectedObj is None: return self._selectedObj.resetScale() + self._selectObject(self._selectedObj) + self.updateProfileToControls() + self.sceneUpdated() def OnScaleMax(self, button): if self._selectedObj is None: diff --git a/Cura/gui/tools/batchRun.py b/Cura/gui/tools/batchRun.py index ea4790bd..c0203468 100644 --- a/Cura/gui/tools/batchRun.py +++ b/Cura/gui/tools/batchRun.py @@ -14,7 +14,7 @@ class batchRunWindow(wx.Frame): self.list = [] - self.SetDropTarget(dropTarget.FileDropTarget(self.OnDropFiles, meshLoader.supportedExtensions())) + self.SetDropTarget(dropTarget.FileDropTarget(self.OnDropFiles, meshLoader.loadSupportedExtensions())) wx.EVT_CLOSE(self, self.OnClose) self.panel = wx.Panel(self, -1) diff --git a/Cura/gui/tools/minecraftImport.py b/Cura/gui/tools/minecraftImport.py index ea230f10..3023fba1 100644 --- a/Cura/gui/tools/minecraftImport.py +++ b/Cura/gui/tools/minecraftImport.py @@ -211,7 +211,9 @@ class minecraftImportWindow(wx.Frame): faceCount += 1 if y == sy - 1 or not self.isSolid[blocks[x, y + 1, z]]: faceCount += 1 - m = mesh.mesh() + + obj = mesh.printableObject() + m = obj._addMesh() m._prepareFaceCount(faceCount * 2) for x in xrange(0, sx): for y in xrange(0, sy): @@ -247,6 +249,5 @@ class minecraftImportWindow(wx.Frame): m._addFace(x+1, y+1, z+1, x+1, y+1, z, x, y+1, z+1) - stlFilename = os.path.join(os.path.dirname(self.level.filename), 'export.stl') - stl.saveAsSTL(m, stlFilename) - self.GetParent().scene.loadScene([stlFilename]) + obj._postProcessAfterLoad() + self.GetParent().scene._scene.add(obj) diff --git a/Cura/gui/util/openglGui.py b/Cura/gui/util/openglGui.py index e94c83af..adbbf001 100644 --- a/Cura/gui/util/openglGui.py +++ b/Cura/gui/util/openglGui.py @@ -470,10 +470,11 @@ class glButton(glGuiControl): glColor4ub(255,255,255,255) opengl.glDrawStringCenter(self._tooltip) glPopMatrix() - if self._progressBar is not None: + progress = self._progressBar + if progress is not None: glColor4ub(255,255,255,192) opengl.glDrawTexturedQuad(pos[0]-bs/2, pos[1]+bs/2, bs, bs / 4, 0) - opengl.glDrawTexturedQuad(pos[0]-bs/2, pos[1]+bs/2, bs * self._progressBar, bs / 4, 0) + opengl.glDrawTexturedQuad(pos[0]-bs/2, pos[1]+bs/2, bs * progress, bs / 4, 0) elif len(self._altTooltip) > 0: glPushMatrix() glTranslatef(pos[0], pos[1], 0) diff --git a/Cura/util/mesh.py b/Cura/util/mesh.py index d2cb0988..a5fd0f03 100644 --- a/Cura/util/mesh.py +++ b/Cura/util/mesh.py @@ -21,18 +21,21 @@ class printableObject(object): def copy(self): ret = printableObject() ret._matrix = self._matrix.copy() - ret._meshList = self._meshList[:] - ret._transformedMin = self._transformedMin - ret._transformedMax = self._transformedMin - ret._transformedSize = self._transformedSize + ret._transformedMin = self._transformedMin.copy() + ret._transformedMax = self._transformedMin.copy() + ret._transformedSize = self._transformedSize.copy() ret._boundaryCircleSize = self._boundaryCircleSize - ret._drawOffset = self._drawOffset - for m in ret._meshList: - m.vbo.incRef() + ret._drawOffset = self._drawOffset.copy() + for m in self._meshList[:]: + m2 = ret._addMesh() + m2.vertexes = m.vertexes + m2.vertexCount = m.vertexCount + m2.vbo = m.vbo + m2.vbo.incRef() return ret def _addMesh(self): - m = mesh() + m = mesh(self) self._meshList.append(m) return m @@ -51,7 +54,7 @@ class printableObject(object): self._boundaryCircleSize = 0 for m in self._meshList: - transformedVertexes = (numpy.matrix(m.vertexes, copy = False) * numpy.matrix(self._matrix, numpy.float32)).getA() + transformedVertexes = m.getTransformedVertexes() transformedMin = transformedVertexes.min(0) transformedMax = transformedVertexes.max(0) for n in xrange(0, 3): @@ -136,7 +139,7 @@ class printableObject(object): self.processMatrix() def layFlat(self): - transformedVertexes = (numpy.matrix(self._meshList[0].vertexes, copy = False) * self._matrix).getA() + transformedVertexes = self.getTransformedVertexes() minZvertex = transformedVertexes[transformedVertexes.argmin(0)[2]] dotMin = 1.0 dotV = None @@ -157,7 +160,7 @@ class printableObject(object): self._matrix *= numpy.matrix([[math.cos(rad), 0, math.sin(rad)], [0,1,0], [-math.sin(rad), 0, math.cos(rad)]], numpy.float64) - transformedVertexes = (numpy.matrix(self._meshList[0].vertexes, copy = False) * self._matrix).getA() + transformedVertexes = self.getTransformedVertexes() minZvertex = transformedVertexes[transformedVertexes.argmin(0)[2]] dotMin = 1.0 dotV = None @@ -194,19 +197,15 @@ class printableObject(object): def split(self, callback): ret = [] for oriMesh in self._meshList: - for m in oriMesh.split(callback): - obj = printableObject() - obj._meshList.append(m) - obj._matrix = self._matrix.copy() - obj._postProcessAfterLoad() - ret.append(obj) + ret += oriMesh.split(callback) return ret class mesh(object): - def __init__(self): + def __init__(self, obj): self.vertexes = None self.vertexCount = 0 self.vbo = None + self._obj = obj def _addFace(self, x0, y0, z0, x1, y1, z1, x2, y2, z2): n = self.vertexCount @@ -255,6 +254,16 @@ class mesh(object): if numpy.linalg.norm(self.vertexes[i] - self.vertexes[idx]) < 0.001: return i + def getTransformedVertexes(self, applyOffsets = False): + if applyOffsets: + pos = self._obj._position.copy() + pos.resize((3)) + pos[2] = self._obj.getSize()[2] / 2 + offset = self._obj._drawOffset.copy() + offset[2] += self._obj.getSize()[2] / 2 + return (numpy.matrix(self.vertexes, copy = False) * numpy.matrix(self._obj._matrix, numpy.float32)).getA() - offset + pos + return (numpy.matrix(self.vertexes, copy = False) * numpy.matrix(self._obj._matrix, numpy.float32)).getA() + def split(self, callback): vertexMap = {} @@ -295,7 +304,9 @@ class mesh(object): doneSet.add(i) todoList.append(i) - m = mesh() + obj = printableObject() + obj._matrix = self._obj._matrix.copy() + m = obj._addMesh() m._prepareFaceCount(len(meshFaceList)) for idx in meshFaceList: m.vertexes[m.vertexCount] = self.vertexes[faceList[idx][0]] @@ -304,5 +315,6 @@ class mesh(object): m.vertexCount += 1 m.vertexes[m.vertexCount] = self.vertexes[faceList[idx][2]] m.vertexCount += 1 - ret.append(m) + obj._postProcessAfterLoad() + ret.append(obj) return ret diff --git a/Cura/util/meshLoader.py b/Cura/util/meshLoader.py index 5be4280b..6f81cf3d 100644 --- a/Cura/util/meshLoader.py +++ b/Cura/util/meshLoader.py @@ -5,11 +5,18 @@ from Cura.util.meshLoaders import obj from Cura.util.meshLoaders import dae from Cura.util.meshLoaders import amf -def supportedExtensions(): +def loadSupportedExtensions(): return ['.stl', '.obj', '.dae', '.amf'] -def wildcardFilter(): - wildcardList = ';'.join(map(lambda s: '*' + s, supportedExtensions())) +def saveSupportedExtensions(): + return ['.amf', '.stl'] + +def loadWildcardFilter(): + wildcardList = ';'.join(map(lambda s: '*' + s, loadSupportedExtensions())) + return "Mesh files (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper()) + +def saveWildcardFilter(): + wildcardList = ';'.join(map(lambda s: '*' + s, saveSupportedExtensions())) return "Mesh files (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper()) #loadMeshes loads 1 or more printableObjects from a file. @@ -30,3 +37,13 @@ def loadMeshes(filename): return amf.loadScene(filename) print 'Error: Unknown model extension: %s' % (ext) return [] + +def saveMeshes(filename, objects): + ext = filename[filename.rfind('.'):].lower() + if ext == '.stl': + stl.saveScene(filename, objects) + return + if ext == '.amf': + amf.saveScene(filename, objects) + return + print 'Error: Unknown model extension: %s' % (ext) diff --git a/Cura/util/meshLoaders/stl.py b/Cura/util/meshLoaders/stl.py index ab9c9354..de706167 100644 --- a/Cura/util/meshLoaders/stl.py +++ b/Cura/util/meshLoaders/stl.py @@ -51,28 +51,28 @@ def loadScene(filename): obj._postProcessAfterLoad() return [obj] -def saveAsSTL(mesh, filename): +def saveScene(filename, objects): f = open(filename, 'wb') #Write the STL binary header. This can contain any info, except for "SOLID" at the start. f.write(("CURA BINARY STL EXPORT. " + time.strftime('%a %d %b %Y %H:%M:%S')).ljust(80, '\000')) - #Next follow 4 binary bytes containing the amount of faces, and then the face information. - f.write(struct.pack("