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 | 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, | ... | ... |