Commit ab04cc4e116ce5557d38105b26f8b5b776ebdfa9
Committed by
GitHub
1 parent
da24d361
Exists in
master
and in
11 other branches
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 | 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, | ... | ... |