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 150 Publisher.subscribe(self.OnRemove,"Remove surfaces")
151 151 Publisher.subscribe(self.UpdateSurfaceInterpolation, 'Update Surface Interpolation')
152 152  
  153 + Publisher.subscribe(self.OnImportSurfaceFile, 'Import surface file')
  154 +
153 155 def OnDuplicate(self, pubsub_evt):
154 156 selected_items = pubsub_evt.data
155 157 proj = prj.Project()
... ... @@ -241,6 +243,36 @@ class SurfaceManager():
241 243 new_index = self.CreateSurfaceFromPolydata(new_polydata)
242 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 276 def CreateSurfaceFromPolydata(self, polydata, overwrite=False,
245 277 name=None, colour=None,
246 278 transparency=None, volume=None, area=None):
... ...
invesalius/gui/data_notebook.py
... ... @@ -40,7 +40,7 @@ import invesalius.gui.widgets.listctrl as listmix
40 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 45 TYPE = {const.LINEAR: _(u"Linear"),
46 46 const.ANGULAR: _(u"Angular"),
... ... @@ -162,15 +162,20 @@ class MeasureButtonControlPanel(wx.Panel):
162 162 BMP_NEW,
163 163 style=button_style,
164 164 size = wx.Size(24, 20))
  165 + button_new.SetToolTipString(_("Create a new measure"))
165 166 self.button_new = button_new
  167 +
166 168 button_remove = pbtn.PlateButton(self, BTN_REMOVE, "",
167 169 BMP_REMOVE,
168 170 style=button_style,
169 171 size = wx.Size(24, 20))
  172 + button_remove.SetToolTipString(_("Remove measure"))
  173 +
170 174 button_duplicate = pbtn.PlateButton(self, BTN_DUPLICATE, "",
171 175 BMP_DUPLICATE,
172 176 style=button_style,
173 177 size = wx.Size(24, 20))
  178 + button_duplicate.SetToolTipString(_("Duplicate measure"))
174 179 button_duplicate.Disable()
175 180  
176 181 # Add all controls to gui
... ... @@ -275,14 +280,19 @@ class ButtonControlPanel(wx.Panel):
275 280 BMP_NEW,
276 281 style=button_style,
277 282 size = wx.Size(24, 20))
  283 + button_new.SetToolTipString(_("Create a new mask"))
  284 +
278 285 button_remove = pbtn.PlateButton(self, BTN_REMOVE, "",
279 286 BMP_REMOVE,
280 287 style=button_style,
281 288 size = wx.Size(24, 20))
  289 + button_remove.SetToolTipString(_("Remove mask"))
  290 +
282 291 button_duplicate = pbtn.PlateButton(self, BTN_DUPLICATE, "",
283 292 BMP_DUPLICATE,
284 293 style=button_style,
285 294 size = wx.Size(24, 20))
  295 + button_duplicate.SetToolTipString(_("Duplicate mask"))
286 296  
287 297 # Add all controls to gui
288 298 sizer = wx.BoxSizer(wx.HORIZONTAL)
... ... @@ -593,6 +603,7 @@ class SurfaceButtonControlPanel(wx.Panel):
593 603 wx.BITMAP_TYPE_PNG)
594 604 BMP_DUPLICATE = wx.Bitmap(os.path.join(const.ICON_DIR, "data_duplicate.png"),
595 605 wx.BITMAP_TYPE_PNG)
  606 + BMP_OPEN = wx.ArtProvider.GetBitmap(wx.ART_FOLDER_OPEN, wx.ART_BUTTON, (18,18))
596 607  
597 608 # Plate buttons based on previous bitmaps
598 609 button_style = pbtn.PB_STYLE_SQUARE | pbtn.PB_STYLE_DEFAULT
... ... @@ -600,20 +611,32 @@ class SurfaceButtonControlPanel(wx.Panel):
600 611 BMP_NEW,
601 612 style=button_style,
602 613 size = wx.Size(24, 20))
  614 + button_new.SetToolTipString(_("Create a new surface"))
  615 +
603 616 button_remove = pbtn.PlateButton(self, BTN_REMOVE, "",
604 617 BMP_REMOVE,
605 618 style=button_style,
606 619 size = wx.Size(24, 20))
  620 + button_remove.SetToolTipString(_("Remove surface"))
  621 +
607 622 button_duplicate = pbtn.PlateButton(self, BTN_DUPLICATE, "",
608 623 BMP_DUPLICATE,
609 624 style=button_style,
610 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 634 # Add all controls to gui
613 635 sizer = wx.BoxSizer(wx.HORIZONTAL)
614 636 sizer.Add(button_new, 0, wx.GROW|wx.EXPAND|wx.LEFT)
615 637 sizer.Add(button_remove, 0, wx.GROW|wx.EXPAND)
616 638 sizer.Add(button_duplicate, 0, wx.GROW|wx.EXPAND)
  639 + sizer.Add(button_open, 0, wx.GROW|wx.EXPAND)
617 640 self.SetSizer(sizer)
618 641 self.Fit()
619 642  
... ... @@ -628,6 +651,8 @@ class SurfaceButtonControlPanel(wx.Panel):
628 651 self.OnRemove()
629 652 elif id == BTN_DUPLICATE:
630 653 self.OnDuplicate()
  654 + elif id == BTN_OPEN:
  655 + self.OnOpenMesh()
631 656  
632 657 def OnNew(self):
633 658 sl = slice_.Slice()
... ... @@ -659,6 +684,11 @@ class SurfaceButtonControlPanel(wx.Panel):
659 684 else:
660 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 693 class SurfacesListCtrlPanel(wx.ListCtrl, listmix.TextEditMixin):
664 694  
... ...
invesalius/gui/dialogs.py
... ... @@ -223,6 +223,12 @@ WILDCARD_NIFTI = "NIfTI 1 (*.nii)|*.nii|" \
223 223 WILDCARD_PARREC = "PAR/REC (*.par)|*.par|" \
224 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 233 def ShowOpenProjectDialog():
228 234 # Default system path
... ... @@ -376,6 +382,39 @@ def ShowImportOtherFilesDialog(id_type):
376 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 418 def ShowSaveAsProjectDialog(default_filename=None):
380 419 current_dir = os.path.abspath(".")
381 420 dlg = wx.FileDialog(None,
... ...