Commit ab04cc4e116ce5557d38105b26f8b5b776ebdfa9

Authored by Thiago Franco de Moraes
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
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,