Commit ab04cc4e116ce5557d38105b26f8b5b776ebdfa9
Committed by
GitHub
1 parent
da24d361
Exists in
master
Import mesh (#68)
Gives invesalius the possibility to import mesh files (STL, PLY, OBJ and VTP) into a project. * Importing mesh file to invesalius * Reading obj mesh files * Changed the import button icon and added a tooltip * Added tooltips to data_notebook buttons * Added error messages when importing surface file
Showing
3 changed files
with
102 additions
and
1 deletions
Show diff stats
invesalius/data/surface.py
@@ -150,6 +150,8 @@ class SurfaceManager(): | @@ -150,6 +150,8 @@ class SurfaceManager(): | ||
150 | Publisher.subscribe(self.OnRemove,"Remove surfaces") | 150 | Publisher.subscribe(self.OnRemove,"Remove surfaces") |
151 | Publisher.subscribe(self.UpdateSurfaceInterpolation, 'Update Surface Interpolation') | 151 | Publisher.subscribe(self.UpdateSurfaceInterpolation, 'Update Surface Interpolation') |
152 | 152 | ||
153 | + Publisher.subscribe(self.OnImportSurfaceFile, 'Import surface file') | ||
154 | + | ||
153 | def OnDuplicate(self, pubsub_evt): | 155 | def OnDuplicate(self, pubsub_evt): |
154 | selected_items = pubsub_evt.data | 156 | selected_items = pubsub_evt.data |
155 | proj = prj.Project() | 157 | proj = prj.Project() |
@@ -241,6 +243,36 @@ class SurfaceManager(): | @@ -241,6 +243,36 @@ class SurfaceManager(): | ||
241 | new_index = self.CreateSurfaceFromPolydata(new_polydata) | 243 | new_index = self.CreateSurfaceFromPolydata(new_polydata) |
242 | Publisher.sendMessage('Show single surface', (new_index, True)) | 244 | Publisher.sendMessage('Show single surface', (new_index, True)) |
243 | 245 | ||
246 | + def OnImportSurfaceFile(self, pubsub_evt): | ||
247 | + """ | ||
248 | + Creates a new surface from a surface file (STL, PLY, OBJ or VTP) | ||
249 | + """ | ||
250 | + filename = pubsub_evt.data | ||
251 | + self.CreateSurfaceFromFile(filename) | ||
252 | + | ||
253 | + def CreateSurfaceFromFile(self, filename): | ||
254 | + if filename.lower().endswith('.stl'): | ||
255 | + reader = vtk.vtkSTLReader() | ||
256 | + elif filename.lower().endswith('.ply'): | ||
257 | + reader = vtk.vtkPLYReader() | ||
258 | + elif filename.lower().endswith('.obj'): | ||
259 | + reader = vtk.vtkOBJReader() | ||
260 | + elif filename.lower().endswith('.vtp'): | ||
261 | + reader = vtk.vtkXMLPolyDataReader() | ||
262 | + else: | ||
263 | + wx.MessageBox(_("File format not reconized by InVesalius"), _("Import surface error")) | ||
264 | + return | ||
265 | + | ||
266 | + reader.SetFileName(filename) | ||
267 | + reader.Update() | ||
268 | + polydata = reader.GetOutput() | ||
269 | + | ||
270 | + if polydata.GetNumberOfPoints() == 0: | ||
271 | + wx.MessageBox(_("InVesalius was not able to import this surface"), _("Import surface error")) | ||
272 | + else: | ||
273 | + name = os.path.splitext(os.path.split(filename)[-1])[0] | ||
274 | + self.CreateSurfaceFromPolydata(polydata, name=name) | ||
275 | + | ||
244 | def CreateSurfaceFromPolydata(self, polydata, overwrite=False, | 276 | def CreateSurfaceFromPolydata(self, polydata, overwrite=False, |
245 | name=None, colour=None, | 277 | name=None, colour=None, |
246 | transparency=None, volume=None, area=None): | 278 | transparency=None, volume=None, area=None): |
invesalius/gui/data_notebook.py
@@ -40,7 +40,7 @@ import invesalius.gui.widgets.listctrl as listmix | @@ -40,7 +40,7 @@ import invesalius.gui.widgets.listctrl as listmix | ||
40 | import invesalius.utils as ul | 40 | import invesalius.utils as ul |
41 | 41 | ||
42 | 42 | ||
43 | -BTN_NEW, BTN_REMOVE, BTN_DUPLICATE = [wx.NewId() for i in xrange(3)] | 43 | +BTN_NEW, BTN_REMOVE, BTN_DUPLICATE, BTN_OPEN = [wx.NewId() for i in xrange(4)] |
44 | 44 | ||
45 | TYPE = {const.LINEAR: _(u"Linear"), | 45 | TYPE = {const.LINEAR: _(u"Linear"), |
46 | const.ANGULAR: _(u"Angular"), | 46 | const.ANGULAR: _(u"Angular"), |
@@ -162,15 +162,20 @@ class MeasureButtonControlPanel(wx.Panel): | @@ -162,15 +162,20 @@ class MeasureButtonControlPanel(wx.Panel): | ||
162 | BMP_NEW, | 162 | BMP_NEW, |
163 | style=button_style, | 163 | style=button_style, |
164 | size = wx.Size(24, 20)) | 164 | size = wx.Size(24, 20)) |
165 | + button_new.SetToolTipString(_("Create a new measure")) | ||
165 | self.button_new = button_new | 166 | self.button_new = button_new |
167 | + | ||
166 | button_remove = pbtn.PlateButton(self, BTN_REMOVE, "", | 168 | button_remove = pbtn.PlateButton(self, BTN_REMOVE, "", |
167 | BMP_REMOVE, | 169 | BMP_REMOVE, |
168 | style=button_style, | 170 | style=button_style, |
169 | size = wx.Size(24, 20)) | 171 | size = wx.Size(24, 20)) |
172 | + button_remove.SetToolTipString(_("Remove measure")) | ||
173 | + | ||
170 | button_duplicate = pbtn.PlateButton(self, BTN_DUPLICATE, "", | 174 | button_duplicate = pbtn.PlateButton(self, BTN_DUPLICATE, "", |
171 | BMP_DUPLICATE, | 175 | BMP_DUPLICATE, |
172 | style=button_style, | 176 | style=button_style, |
173 | size = wx.Size(24, 20)) | 177 | size = wx.Size(24, 20)) |
178 | + button_duplicate.SetToolTipString(_("Duplicate measure")) | ||
174 | button_duplicate.Disable() | 179 | button_duplicate.Disable() |
175 | 180 | ||
176 | # Add all controls to gui | 181 | # Add all controls to gui |
@@ -275,14 +280,19 @@ class ButtonControlPanel(wx.Panel): | @@ -275,14 +280,19 @@ class ButtonControlPanel(wx.Panel): | ||
275 | BMP_NEW, | 280 | BMP_NEW, |
276 | style=button_style, | 281 | style=button_style, |
277 | size = wx.Size(24, 20)) | 282 | size = wx.Size(24, 20)) |
283 | + button_new.SetToolTipString(_("Create a new mask")) | ||
284 | + | ||
278 | button_remove = pbtn.PlateButton(self, BTN_REMOVE, "", | 285 | button_remove = pbtn.PlateButton(self, BTN_REMOVE, "", |
279 | BMP_REMOVE, | 286 | BMP_REMOVE, |
280 | style=button_style, | 287 | style=button_style, |
281 | size = wx.Size(24, 20)) | 288 | size = wx.Size(24, 20)) |
289 | + button_remove.SetToolTipString(_("Remove mask")) | ||
290 | + | ||
282 | button_duplicate = pbtn.PlateButton(self, BTN_DUPLICATE, "", | 291 | button_duplicate = pbtn.PlateButton(self, BTN_DUPLICATE, "", |
283 | BMP_DUPLICATE, | 292 | BMP_DUPLICATE, |
284 | style=button_style, | 293 | style=button_style, |
285 | size = wx.Size(24, 20)) | 294 | size = wx.Size(24, 20)) |
295 | + button_duplicate.SetToolTipString(_("Duplicate mask")) | ||
286 | 296 | ||
287 | # Add all controls to gui | 297 | # Add all controls to gui |
288 | sizer = wx.BoxSizer(wx.HORIZONTAL) | 298 | sizer = wx.BoxSizer(wx.HORIZONTAL) |
@@ -593,6 +603,7 @@ class SurfaceButtonControlPanel(wx.Panel): | @@ -593,6 +603,7 @@ class SurfaceButtonControlPanel(wx.Panel): | ||
593 | wx.BITMAP_TYPE_PNG) | 603 | wx.BITMAP_TYPE_PNG) |
594 | BMP_DUPLICATE = wx.Bitmap(os.path.join(const.ICON_DIR, "data_duplicate.png"), | 604 | BMP_DUPLICATE = wx.Bitmap(os.path.join(const.ICON_DIR, "data_duplicate.png"), |
595 | wx.BITMAP_TYPE_PNG) | 605 | wx.BITMAP_TYPE_PNG) |
606 | + BMP_OPEN = wx.ArtProvider.GetBitmap(wx.ART_FOLDER_OPEN, wx.ART_BUTTON, (18,18)) | ||
596 | 607 | ||
597 | # Plate buttons based on previous bitmaps | 608 | # Plate buttons based on previous bitmaps |
598 | button_style = pbtn.PB_STYLE_SQUARE | pbtn.PB_STYLE_DEFAULT | 609 | button_style = pbtn.PB_STYLE_SQUARE | pbtn.PB_STYLE_DEFAULT |
@@ -600,20 +611,32 @@ class SurfaceButtonControlPanel(wx.Panel): | @@ -600,20 +611,32 @@ class SurfaceButtonControlPanel(wx.Panel): | ||
600 | BMP_NEW, | 611 | BMP_NEW, |
601 | style=button_style, | 612 | style=button_style, |
602 | size = wx.Size(24, 20)) | 613 | size = wx.Size(24, 20)) |
614 | + button_new.SetToolTipString(_("Create a new surface")) | ||
615 | + | ||
603 | button_remove = pbtn.PlateButton(self, BTN_REMOVE, "", | 616 | button_remove = pbtn.PlateButton(self, BTN_REMOVE, "", |
604 | BMP_REMOVE, | 617 | BMP_REMOVE, |
605 | style=button_style, | 618 | style=button_style, |
606 | size = wx.Size(24, 20)) | 619 | size = wx.Size(24, 20)) |
620 | + button_remove.SetToolTipString(_("Remove surface")) | ||
621 | + | ||
607 | button_duplicate = pbtn.PlateButton(self, BTN_DUPLICATE, "", | 622 | button_duplicate = pbtn.PlateButton(self, BTN_DUPLICATE, "", |
608 | BMP_DUPLICATE, | 623 | BMP_DUPLICATE, |
609 | style=button_style, | 624 | style=button_style, |
610 | size = wx.Size(24, 20)) | 625 | size = wx.Size(24, 20)) |
626 | + button_duplicate.SetToolTipString(_("Duplicate surface")) | ||
627 | + | ||
628 | + button_open = pbtn.PlateButton(self, BTN_OPEN, "", | ||
629 | + BMP_OPEN, | ||
630 | + style=button_style, | ||
631 | + size = wx.Size(24, 20)) | ||
632 | + button_open.SetToolTipString(_("Import a surface file into InVesalius")) | ||
611 | 633 | ||
612 | # Add all controls to gui | 634 | # Add all controls to gui |
613 | sizer = wx.BoxSizer(wx.HORIZONTAL) | 635 | sizer = wx.BoxSizer(wx.HORIZONTAL) |
614 | sizer.Add(button_new, 0, wx.GROW|wx.EXPAND|wx.LEFT) | 636 | sizer.Add(button_new, 0, wx.GROW|wx.EXPAND|wx.LEFT) |
615 | sizer.Add(button_remove, 0, wx.GROW|wx.EXPAND) | 637 | sizer.Add(button_remove, 0, wx.GROW|wx.EXPAND) |
616 | sizer.Add(button_duplicate, 0, wx.GROW|wx.EXPAND) | 638 | sizer.Add(button_duplicate, 0, wx.GROW|wx.EXPAND) |
639 | + sizer.Add(button_open, 0, wx.GROW|wx.EXPAND) | ||
617 | self.SetSizer(sizer) | 640 | self.SetSizer(sizer) |
618 | self.Fit() | 641 | self.Fit() |
619 | 642 | ||
@@ -628,6 +651,8 @@ class SurfaceButtonControlPanel(wx.Panel): | @@ -628,6 +651,8 @@ class SurfaceButtonControlPanel(wx.Panel): | ||
628 | self.OnRemove() | 651 | self.OnRemove() |
629 | elif id == BTN_DUPLICATE: | 652 | elif id == BTN_DUPLICATE: |
630 | self.OnDuplicate() | 653 | self.OnDuplicate() |
654 | + elif id == BTN_OPEN: | ||
655 | + self.OnOpenMesh() | ||
631 | 656 | ||
632 | def OnNew(self): | 657 | def OnNew(self): |
633 | sl = slice_.Slice() | 658 | sl = slice_.Slice() |
@@ -659,6 +684,11 @@ class SurfaceButtonControlPanel(wx.Panel): | @@ -659,6 +684,11 @@ class SurfaceButtonControlPanel(wx.Panel): | ||
659 | else: | 684 | else: |
660 | dlg.SurfaceSelectionRequiredForDuplication() | 685 | dlg.SurfaceSelectionRequiredForDuplication() |
661 | 686 | ||
687 | + def OnOpenMesh(self): | ||
688 | + filename = dlg.ShowImportMeshFilesDialog() | ||
689 | + if filename: | ||
690 | + Publisher.sendMessage('Import surface file', filename) | ||
691 | + | ||
662 | 692 | ||
663 | class SurfacesListCtrlPanel(wx.ListCtrl, listmix.TextEditMixin): | 693 | class SurfacesListCtrlPanel(wx.ListCtrl, listmix.TextEditMixin): |
664 | 694 |
invesalius/gui/dialogs.py
@@ -223,6 +223,12 @@ WILDCARD_NIFTI = "NIfTI 1 (*.nii)|*.nii|" \ | @@ -223,6 +223,12 @@ WILDCARD_NIFTI = "NIfTI 1 (*.nii)|*.nii|" \ | ||
223 | WILDCARD_PARREC = "PAR/REC (*.par)|*.par|" \ | 223 | WILDCARD_PARREC = "PAR/REC (*.par)|*.par|" \ |
224 | "All files (*.*)|*.*" | 224 | "All files (*.*)|*.*" |
225 | 225 | ||
226 | +WILDCARD_MESH_FILES = "STL File format (*.stl)|*.stl|" \ | ||
227 | + "Standard Polygon File Format (*.ply)|*.ply|" \ | ||
228 | + "Alias Wavefront Object (*.obj)|*.obj|" \ | ||
229 | + "VTK Polydata File Format (*.vtp)|*.vtp|" \ | ||
230 | + "All files (*.*)|*.*" | ||
231 | + | ||
226 | 232 | ||
227 | def ShowOpenProjectDialog(): | 233 | def ShowOpenProjectDialog(): |
228 | # Default system path | 234 | # Default system path |
@@ -376,6 +382,39 @@ def ShowImportOtherFilesDialog(id_type): | @@ -376,6 +382,39 @@ def ShowImportOtherFilesDialog(id_type): | ||
376 | return filename | 382 | return filename |
377 | 383 | ||
378 | 384 | ||
385 | +def ShowImportMeshFilesDialog(): | ||
386 | + # Default system path | ||
387 | + current_dir = os.path.abspath(".") | ||
388 | + dlg = wx.FileDialog(None, message=_("Import surface file"), | ||
389 | + wildcard=WILDCARD_MESH_FILES, | ||
390 | + style=wx.FD_OPEN | wx.FD_CHANGE_DIR) | ||
391 | + | ||
392 | + # stl filter is default | ||
393 | + dlg.SetFilterIndex(0) | ||
394 | + | ||
395 | + # Show the dialog and retrieve the user response. If it is the OK response, | ||
396 | + # process the data. | ||
397 | + filename = None | ||
398 | + try: | ||
399 | + if dlg.ShowModal() == wx.ID_OK: | ||
400 | + # GetPath returns in unicode, if a path has non-ascii characters a | ||
401 | + # UnicodeEncodeError is raised. To avoid this, path is encoded in utf-8 | ||
402 | + if sys.platform == "win32": | ||
403 | + filename = dlg.GetPath() | ||
404 | + else: | ||
405 | + filename = dlg.GetPath().encode('utf-8') | ||
406 | + | ||
407 | + except(wx._core.PyAssertionError): # TODO: error win64 | ||
408 | + if (dlg.GetPath()): | ||
409 | + filename = dlg.GetPath() | ||
410 | + | ||
411 | + # Destroy the dialog. Don't do this until you are done with it! | ||
412 | + # BAD things can happen otherwise! | ||
413 | + dlg.Destroy() | ||
414 | + os.chdir(current_dir) | ||
415 | + return filename | ||
416 | + | ||
417 | + | ||
379 | def ShowSaveAsProjectDialog(default_filename=None): | 418 | def ShowSaveAsProjectDialog(default_filename=None): |
380 | current_dir = os.path.abspath(".") | 419 | current_dir = os.path.abspath(".") |
381 | dlg = wx.FileDialog(None, | 420 | dlg = wx.FileDialog(None, |