diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..cc7abe3
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,36 @@
+---
+name: Bug report
+about: Report problems or unexpected behavior
+title: ''
+labels: type:bug
+assignees: ''
+
+---
+
+## Summary
+
+
+
+## Steps to reproduce
+
+
+
+## Environment
+- BoneReconstructionPlanner version: Slicer-5.??.?-YYYY-MM-DD
+- Operating system: Windows / Linux / Mac + which version
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000..df60e62
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,24 @@
+---
+name: Feature request
+about: Suggest a new feature or enhancement for this project
+title: ''
+labels: type:enhancement
+assignees: ''
+
+---
+
+## Is your feature request related to a problem? Please describe.
+
+
+
+## Describe the solution you'd like
+
+
+
+## Describe alternatives you've considered
+
+
+
+## Additional context
+
+
\ No newline at end of file
diff --git a/BoneReconstructionPlanner/BRPLib/guiWidgets.py b/BoneReconstructionPlanner/BRPLib/guiWidgets.py
new file mode 100644
index 0000000..3be192a
--- /dev/null
+++ b/BoneReconstructionPlanner/BRPLib/guiWidgets.py
@@ -0,0 +1,32 @@
+from __main__ import ctk, qt
+
+class checkablePushButtonWithIcon(ctk.ctkCheckablePushButton):
+ def __init__(self, text="", icon=qt.QIcon(), parent=None):
+ super().__init__(parent)
+ self.text = text
+ #
+ self._mainLayout = qt.QHBoxLayout(self)
+ self._mainLayout.setContentsMargins(0, 0, 0, 0)
+ #self._mainLayout.setSpacing(15)
+ #self._mainLayout.setSpacing(300)
+ #
+ self.icon = icon
+ self._iconSize = qt.QSize(36, 36)
+ pixmap = self.icon.pixmap(self._iconSize)
+ self._iconLabel = qt.QLabel()
+ self._iconLabel.setAlignment(qt.Qt.AlignCenter)
+ self._iconLabel.setPixmap(pixmap)
+ self._iconLabel.setFixedSize(self._iconSize)
+ self._mainLayout.addSpacing(self.sizeHint.width())
+ self._mainLayout.addWidget(self._iconLabel)
+ self.setMinimumWidth(self.sizeHint.width() + self._iconSize.width())
+ self.setSizePolicy(qt.QSizePolicy.Maximum, qt.QSizePolicy.Fixed)
+
+# use it with ctk.ctkCheckBox.indicatorIcon = iconWithGreyOut(iconPath)
+def iconWithGreyOut(iconPath, size = qt.QSize(24, 24)):
+ iconPixmap = qt.QPixmap(iconPath)
+ iconWithGreyOut = qt.QIcon()
+ iconWithGreyOut.addPixmap(iconPixmap, qt.QIcon.Normal, qt.QIcon.On)
+ grayed = iconWithGreyOut.pixmap(size, qt.QIcon.Disabled, qt.QIcon.On)
+ iconWithGreyOut.addPixmap(grayed, qt.QIcon.Normal, qt.QIcon.Off)
+ return iconWithGreyOut
\ No newline at end of file
diff --git a/BoneReconstructionPlanner/BoneReconstructionPlanner.py b/BoneReconstructionPlanner/BoneReconstructionPlanner.py
index dda7bd0..eac24e8 100644
--- a/BoneReconstructionPlanner/BoneReconstructionPlanner.py
+++ b/BoneReconstructionPlanner/BoneReconstructionPlanner.py
@@ -6,6 +6,7 @@
from slicer.ScriptedLoadableModule import *
from slicer.util import VTKObservationMixin
from BRPLib.helperFunctions import *
+from BRPLib.guiWidgets import *
#
# BoneReconstructionPlanner
@@ -215,6 +216,7 @@ def __init__(self, parent=None):
"""
Called when the user opens the module the first time and the widget is initialized.
"""
+ self.version = "5.6.2.10"
ScriptedLoadableModuleWidget.__init__(self, parent)
VTKObservationMixin.__init__(self) # needed for parameter node observation
self.logic = None
@@ -238,8 +240,63 @@ def setup(self):
# "setMRMLScene(vtkMRMLScene*)" slot.
uiWidget.setMRMLScene(slicer.mrmlScene)
+
+ # additional UI setup
+ self.ui.versionLabel.text = f"Version: {self.version}"
+
+ import os
+ updatePlanningIconPath = os.path.join(os.path.dirname(__file__), 'Resources/Icons/update_48.svg')
+
+ generateFibulaPlanesFibulaBonePiecesAndTransformThemToMandibleButton = checkablePushButtonWithIcon(
+ "Update fibula planes over fibula line; update fibula bone pieces \nand transform them to mandible",
+ qt.QIcon(updatePlanningIconPath)
+ )
+
+ updateVSPButtonsLayout = self.ui.updateVSPButtonsFrame.layout()
+ updateVSPButtonsLayout.insertWidget(0, generateFibulaPlanesFibulaBonePiecesAndTransformThemToMandibleButton)
+
+ self.ui.generateFibulaPlanesFibulaBonePiecesAndTransformThemToMandibleButton = generateFibulaPlanesFibulaBonePiecesAndTransformThemToMandibleButton
+
+ mailIconPath = os.path.join(os.path.dirname(__file__), 'Resources/Icons/mail_48.svg')
+ self.ui.emailBugReportButton.setIcon(qt.QIcon(mailIconPath))
+ self.ui.emailFeatureRequestButton.setIcon(qt.QIcon(mailIconPath))
+
+ openDocumentationIconPath = os.path.join(os.path.dirname(__file__), 'Resources/Icons/quick_reference_48.svg')
+ self.ui.openDocumentationButton.setIcon(qt.QIcon(openDocumentationIconPath))
+
+ boneIconPath = os.path.join(os.path.dirname(__file__), 'Resources/Icons/bone_48.svg')
+ self.ui.makeModelsButton.setIcon(qt.QIcon(boneIconPath))
+
+ targetIconPath = os.path.join(os.path.dirname(__file__), 'Resources/Icons/target_48.svg')
+ self.ui.centerFibulaLineButton.setIcon(qt.QIcon(targetIconPath))
+
+ recycleIconPath = os.path.join(os.path.dirname(__file__), 'Resources/Icons/recycle_48.svg')
+ self.ui.hardVSPUpdateButton.setIcon(qt.QIcon(recycleIconPath))
+
+ lockIconPath = os.path.join(os.path.dirname(__file__), 'Resources/Icons/lock_48.svg')
+ self.ui.lockVSPButton.setIcon(qt.QIcon(lockIconPath))
+
+ visibilityIconPath = os.path.join(os.path.dirname(__file__), 'Resources/Icons/visibility_48.svg')
+ self.ui.showMandiblePlanesToolButton.setIcon(qt.QIcon(visibilityIconPath))
+ self.ui.showMandiblePlanesToolButton.setIconSize(qt.QSize(24,24))
+ self.ui.showMandiblePlanesToolButton.setMinimumSize(24,24)
+
+ axesIconPath = os.path.join(os.path.dirname(__file__), 'Resources/Icons/axes.svg')
+ self.ui.showMandiblePlanesInteractionHandlesToolButton.setIcon(qt.QIcon(axesIconPath))
+ self.ui.showMandiblePlanesInteractionHandlesToolButton.setIconSize(qt.QSize(24,24))
+ self.ui.showMandiblePlanesInteractionHandlesToolButton.setMinimumSize(24,24)
+
+ booleanOperationsIconPath = os.path.join(os.path.dirname(__file__), 'Resources/Icons/construction_48.svg')
+ self.ui.create3DModelOfTheReconstructionButton.setIcon(qt.QIcon(booleanOperationsIconPath))
+ self.ui.makeBooleanOperationsToFibulaSurgicalGuideBaseButton.setIcon(qt.QIcon(booleanOperationsIconPath))
+ self.ui.makeBooleanOperationsToFibulaSurgicalGuideBaseButton.setIconSize(qt.QSize(48,48))
+ self.ui.makeBooleanOperationsToMandibleSurgicalGuideBaseButton.setIcon(qt.QIcon(booleanOperationsIconPath))
+ self.ui.makeBooleanOperationsToMandibleSurgicalGuideBaseButton.setIconSize(qt.QSize(48,48))
+
#self.ui.dentalImplantCylinderSelector.addAttribute('vtkMRMLModelNode','isDentalImplantCylinder','True')
+
+
# Create logic class. Logic implements all computations that should be possible to run
# in batch mode, without a graphical user interface.
self.logic = BoneReconstructionPlannerLogic()
@@ -268,6 +325,7 @@ def setup(self):
self.ui.dentalImplantFiducialListSelector.connect("currentNodeChanged(vtkMRMLNode*)", self.updateParameterNodeFromGUI)
#self.ui.dentalImplantCylinderSelector.connect("currentNodeChanged(vtkMRMLNode*)", self.updateParameterNodeFromGUI)
self.ui.plateCurveSelector.connect("currentNodeChanged(vtkMRMLNode*)", self.updateParameterNodeFromGUI)
+ self.ui.condylarBeamModelSelector.connect("currentNodeChanged(vtkMRMLNode*)", self.updateParameterNodeFromGUI)
self.ui.initialSpinBox.valueChanged.connect(self.updateParameterNodeFromGUI)
self.ui.betweenSpinBox.valueChanged.connect(self.updateParameterNodeFromGUI)
@@ -300,6 +358,9 @@ def setup(self):
self.ui.mandibleSideToRemoveComboBox.currentTextChanged.connect(self.updateParameterNodeFromGUI)
# Buttons
+ self.ui.emailBugReportButton.connect('clicked(bool)',self.onEmailBugReportButton)
+ self.ui.emailFeatureRequestButton.connect('clicked(bool)',self.onEmailFeatureRequestButton)
+ self.ui.openDocumentationButton.connect('clicked(bool)',self.onOpenDocumentationButton)
self.ui.rightSideLegFibulaCheckBox.connect('stateChanged(int)', self.updateParameterNodeFromGUI)
self.ui.addCutPlaneButton.connect('clicked(bool)',self.onAddCutPlaneButton)
self.ui.addMandibularCurveButton.connect('clicked(bool)',self.onAddMandibularCurveButton)
@@ -316,16 +377,13 @@ def setup(self):
self.ui.generateFibulaPlanesFibulaBonePiecesAndTransformThemToMandibleButton.connect('clicked(bool)', self.onGenerateFibulaPlanesFibulaBonePiecesAndTransformThemToMandibleButton)
self.ui.updateFibulaDentalImplantCylindersButton.connect('clicked(bool)', self.onUpdateFibulaDentalImplantCylindersButton)
self.ui.centerFibulaLineButton.connect('clicked(bool)', self.onCenterFibulaLineButton)
- self.ui.showHideBiggerSawBoxesInteractionHandlesButton.connect('clicked(bool)', self.onShowHideBiggerSawBoxesInteractionHandlesButton)
- self.ui.showHideMandiblePlanesInteractionHandlesButton.connect('clicked(bool)', self.onShowHideMandiblePlanesInteractionHandlesButton)
self.ui.create3DModelOfTheReconstructionButton.connect('clicked(bool)', self.onCreate3DModelOfTheReconstructionButton)
- self.ui.showHideFibulaSegmentsLengthsButton.connect('clicked(bool)', self.onShowHideFibulaSegmentsLengthsButton)
- self.ui.showHideOriginalMandibleButton.connect('clicked(bool)', self.onShowHideOriginalMandibleButton)
self.ui.createDentalImplantCylindersFiducialListButton.connect('clicked(bool)', self.onCreateDentalImplantCylindersFiducialListButton)
self.ui.createCylindersFromFiducialListAndNeomandiblePiecesButton.connect('clicked(bool)', self.onCreateCylindersFromFiducialListAndNeomandiblePiecesButton)
self.ui.createPlateCurveButton.connect('clicked(bool)', self.onCreatePlateCurveButton)
self.ui.createCustomPlateButton.connect('clicked(bool)', self.onCreateCustomPlateButton)
self.ui.hardVSPUpdateButton.connect('clicked(bool)', self.onHardVSPUpdateButton)
+ self.ui.lockVSPButton.connect('toggled(bool)', self.onLockVSPButton)
self.ui.makeAllMandiblePlanesRotateTogetherCheckBox.connect('stateChanged(int)', self.updateParameterNodeFromGUI)
self.ui.useMoreExactVersionOfPositioningAlgorithmCheckBox.connect('stateChanged(int)', self.updateParameterNodeFromGUI)
self.ui.useNonDecimatedBoneModelsForPreviewCheckBox.connect('stateChanged(int)', self.updateParameterNodeFromGUI)
@@ -335,20 +393,14 @@ def setup(self):
self.ui.dentalImplantsPlanningAndFibulaDrillGuidesCheckBox.connect('stateChanged(int)', self.updateParameterNodeFromGUI)
self.ui.customTitaniumPlateDesingCheckBox.connect('stateChanged(int)', self.updateParameterNodeFromGUI)
self.ui.makeAllDentalImplanCylindersParallelCheckBox.connect('stateChanged(int)', self.updateParameterNodeFromGUI)
+ self.ui.showFibulaSegmentsLengthsCheckBox.connect('stateChanged(int)', self.updateParameterNodeFromGUI)
+ self.ui.showOriginalMandibleCheckBox.connect('stateChanged(int)', self.updateParameterNodeFromGUI)
+ self.ui.showBiggerSawBoxesInteractionHandlesCheckBox.connect('stateChanged(int)', self.updateParameterNodeFromGUI)
+ self.ui.showMandiblePlanesToolButton.connect('clicked(bool)', self.updateParameterNodeFromGUI)
+ self.ui.showMandiblePlanesInteractionHandlesToolButton.connect('clicked(bool)', self.updateParameterNodeFromGUI)
self.ui.orientation3DCubeCheckBox.connect('stateChanged(int)', self.onOrientation3DCubeCheckBox)
self.ui.lightsRenderingComboBox.textActivated.connect(self.onLightsRenderingComboBox)
- import os
- recycleIconPath = os.path.join(os.path.dirname(__file__), 'Resources/Icons/recycle_48.svg')
- self.ui.hardVSPUpdateButton.setIcon(qt.QIcon(recycleIconPath))
-
- booleanOperationsIconPath = os.path.join(os.path.dirname(__file__), 'Resources/Icons/construction_48.svg')
- self.ui.create3DModelOfTheReconstructionButton.setIcon(qt.QIcon(booleanOperationsIconPath))
- self.ui.makeBooleanOperationsToFibulaSurgicalGuideBaseButton.setIcon(qt.QIcon(booleanOperationsIconPath))
- self.ui.makeBooleanOperationsToFibulaSurgicalGuideBaseButton.setIconSize(qt.QSize(48,48))
- self.ui.makeBooleanOperationsToMandibleSurgicalGuideBaseButton.setIcon(qt.QIcon(booleanOperationsIconPath))
- self.ui.makeBooleanOperationsToMandibleSurgicalGuideBaseButton.setIconSize(qt.QSize(48,48))
-
# Make sure parameter node is initialized (needed for module reload)
self.initializeParameterNode()
@@ -405,17 +457,15 @@ def enter(self):
sawBoxesPlanesList = createListFromFolderID(sawBoxesPlanesFolder)
dentalImplantsPlanesList = createListFromFolderID(dentalImplantsPlanesFolder)
- self.logic.setInteractiveHandlesVisibilityOfMarkups(
- mandibularPlanesList,
- visibility=True
- )
+ #self.setMandiblePlanesInteractionHandlesVisibility(visibility=True)
+ if self._parameterNode.GetParameter("lockVSP") == "False":
+ self._parameterNode.SetParameter("showMandiblePlanesInteractionHandles","True")
self.logic.setMarkupsListLocked(mandibularPlanesList,locked=False)
self.logic.addMandiblePlaneObservers()
- self.logic.setInteractiveHandlesVisibilityOfMarkups(
- sawBoxesPlanesList,
- visibility=True
- )
+ # make it not visible to not clutter the mandible 3D view
+ #self.setBiggerSawBoxesInteractionHandlesVisibility(visibility=False)
+ self._parameterNode.SetParameter("showBiggerSawBoxesInteractionHandles","False")
self.logic.setMarkupsListLocked(sawBoxesPlanesList,locked=False)
self.logic.addSawBoxPlaneObservers()
@@ -444,17 +494,22 @@ def exit(self):
sawBoxesPlanesList = createListFromFolderID(sawBoxesPlanesFolder)
dentalImplantsPlanesList = createListFromFolderID(dentalImplantsPlanesFolder)
- self.logic.setInteractiveHandlesVisibilityOfMarkups(
- mandibularPlanesList,
- visibility=False
- )
+ #self.logic.setInteractiveHandlesVisibilityOfMarkups(
+ # mandibularPlanesList,
+ # visibility=False
+ #)
+ if self._parameterNode.GetParameter("lockVSP") == "False":
+ self._parameterNode.SetParameter("showMandiblePlanesInteractionHandles","False")
+ self.updateGUIFromParameterNode() # needed because parameterNode observer was removed
self.logic.setMarkupsListLocked(mandibularPlanesList,locked=True)
self.logic.removeMandiblePlaneObservers()
- self.logic.setInteractiveHandlesVisibilityOfMarkups(
- sawBoxesPlanesList,
- visibility=False
- )
+ #self.logic.setInteractiveHandlesVisibilityOfMarkups(
+ # sawBoxesPlanesList,
+ # visibility=False
+ #)
+ self._parameterNode.SetParameter("showBiggerSawBoxesInteractionHandles","False")
+ self.updateGUIFromParameterNode() # needed because parameterNode observer was removed
self.logic.setMarkupsListLocked(sawBoxesPlanesList,locked=True)
self.logic.removeSawBoxPlaneObservers()
@@ -547,6 +602,7 @@ def updateGUIFromParameterNode(self, caller=None, event=None):
self.ui.dentalImplantFiducialListSelector.setCurrentNode(self._parameterNode.GetNodeReference("dentalImplantsFiducialList"))
#self.ui.dentalImplantCylinderSelector.setCurrentNode(self._parameterNode.GetNodeReference("selectedDentalImplantCylinderModel"))
self.ui.plateCurveSelector.setCurrentNode(self._parameterNode.GetNodeReference("plateCurve"))
+ self.ui.condylarBeamModelSelector.setCurrentNode(self._parameterNode.GetNodeReference("condylarBeamModel"))
if self._parameterNode.GetNodeReference("fibulaSurgicalGuideBaseModel") is not None:
self.ui.createCylindersFromFiducialListAndFibulaSurgicalGuideBaseButton.enabled = True
@@ -612,7 +668,10 @@ def updateGUIFromParameterNode(self, caller=None, event=None):
self.ui.useMoreExactVersionOfPositioningAlgorithmCheckBox.checked = self._parameterNode.GetParameter("useMoreExactVersionOfPositioningAlgorithm") == "True"
self.ui.useNonDecimatedBoneModelsForPreviewCheckBox.checked = self._parameterNode.GetParameter("useNonDecimatedBoneModelsForPreview") == "True"
self.ui.mandiblePlanesPositioningForMaximumBoneContactCheckBox.checked = self._parameterNode.GetParameter("mandiblePlanesPositioningForMaximumBoneContact") == "True"
- self.ui.checkSecurityMarginOnMiterBoxCreationCheckBox.checked = self._parameterNode.GetParameter("checkSecurityMarginOnMiterBoxCreation") != "False"
+
+ checkSecurityMarginOnMiterBoxCreationChecked = self._parameterNode.GetParameter("checkSecurityMarginOnMiterBoxCreation") != "False"
+ self.ui.checkSecurityMarginOnMiterBoxCreationCheckBox.checked = checkSecurityMarginOnMiterBoxCreationChecked
+ self.ui.securityMarginOfFibulaPiecesFrame.enabled = checkSecurityMarginOnMiterBoxCreationChecked
self.ui.fibulaSegmentsMeasurementModeComboBox.currentText = self._parameterNode.GetParameter("fibulaSegmentsMeasurementMode")
@@ -657,6 +716,54 @@ def updateGUIFromParameterNode(self, caller=None, event=None):
else:
self.ui.customTitaniumPlateGenerationCollapsibleButton.hide()
+
+ lockVSPChecked = self._parameterNode.GetParameter("lockVSP") == "True"
+
+ showMandiblePlanesChecked = self._parameterNode.GetParameter("showMandiblePlanes") == "True"
+ self.ui.showMandiblePlanesToolButton.checked = showMandiblePlanesChecked
+ self.setMandiblePlanesVisibility(showMandiblePlanesChecked)
+
+ showMandiblePlanesInteractionHandlesChecked = self._parameterNode.GetParameter("showMandiblePlanesInteractionHandles") == "True"
+ showMandiblePlanesInteractionHandles = (
+ showMandiblePlanesChecked and showMandiblePlanesInteractionHandlesChecked and
+ (not lockVSPChecked)
+ )
+ self.ui.showMandiblePlanesInteractionHandlesToolButton.checked = (
+ showMandiblePlanesInteractionHandles
+ )
+ self.setMandiblePlanesInteractionHandlesVisibility(showMandiblePlanesInteractionHandles)
+ self.ui.showMandiblePlanesInteractionHandlesToolButton.enabled = (
+ showMandiblePlanesChecked and
+ (not lockVSPChecked)
+ )
+
+
+ shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)
+ mandibularPlanesFolder = shNode.GetItemByName("Mandibular planes")
+ mandibularPlanesList = createListFromFolderID(mandibularPlanesFolder)
+ fibulaLine = self._parameterNode.GetNodeReference("fibulaLine")
+ mandibularCurve = self._parameterNode.GetNodeReference("mandibleCurve")
+ planningObjectsList = mandibularPlanesList + [fibulaLine,mandibularCurve]
+ if lockVSPChecked:
+ self.setMandiblePlanesVisibility(showMandiblePlanesChecked)
+ self.logic.setMarkupsListLocked(planningObjectsList,locked=True)
+ self.logic.removeMandiblePlaneObservers()
+ #
+ self.ui.lockVSPButton.checked = True
+ self.ui.parametersOfVSPFrame.enabled = False
+ self.ui.updateVSPButtonsFrame.enabled = False
+ self.ui.create3DModelOfTheReconstructionFrame.enabled = False
+ else:
+ #self.setMandiblePlanesVisibility(True)
+ self.logic.setMarkupsListLocked(planningObjectsList,locked=False)
+ self.logic.removeMandiblePlaneObservers() # in case they already exist
+ self.logic.addMandiblePlaneObservers()
+ #
+ self.ui.lockVSPButton.checked = False
+ self.ui.parametersOfVSPFrame.enabled = True
+ self.ui.updateVSPButtonsFrame.enabled = True
+ self.ui.create3DModelOfTheReconstructionFrame.enabled = True
+
if self._parameterNode.GetParameter("updateOnMandiblePlanesMovement") == "True":
self.ui.generateFibulaPlanesFibulaBonePiecesAndTransformThemToMandibleButton.checkState = 2
@@ -668,6 +775,18 @@ def updateGUIFromParameterNode(self, caller=None, event=None):
else:
self.ui.updateFibulaDentalImplantCylindersButton.checkState = 0
+ showFibulaSegmentsLengthsChecked = self._parameterNode.GetParameter("showFibulaSegmentsLengths") == "True"
+ self.ui.showFibulaSegmentsLengthsCheckBox.checked = showFibulaSegmentsLengthsChecked
+ self.setFibulaSegmentsLengthsVisibility(showFibulaSegmentsLengthsChecked)
+
+ showOriginalMandibleChecked = self._parameterNode.GetParameter("showOriginalMandible") == "True"
+ self.ui.showOriginalMandibleCheckBox.checked = showOriginalMandibleChecked
+ self.setOriginalMandibleVisility(showOriginalMandibleChecked)
+
+ showBiggerSawBoxesInteractionHandlesChecked = self._parameterNode.GetParameter("showBiggerSawBoxesInteractionHandles") == "True"
+ self.ui.showBiggerSawBoxesInteractionHandlesCheckBox.checked = showBiggerSawBoxesInteractionHandlesChecked
+ self.setBiggerSawBoxesInteractionHandlesVisibility(showBiggerSawBoxesInteractionHandlesChecked)
+
# All the GUI updates are done
self._updatingGUIFromParameterNode = False
@@ -703,6 +822,7 @@ def updateParameterNodeFromGUI(self, caller=None, event=None):
self._parameterNode.SetNodeReferenceID("dentalImplantsFiducialList", self.ui.dentalImplantFiducialListSelector.currentNodeID)
#self._parameterNode.SetNodeReferenceID("selectedDentalImplantCylinderModel", self.ui.dentalImplantCylinderSelector.currentNodeID)
self._parameterNode.SetNodeReferenceID("plateCurve", self.ui.plateCurveSelector.currentNodeID)
+ self._parameterNode.SetNodeReferenceID("condylarBeamModel", self.ui.condylarBeamModelSelector.currentNodeID)
self._parameterNode.SetParameter("initialSpace", str(self.ui.initialSpinBox.value))
self._parameterNode.SetParameter("additionalBetweenSpaceOfFibulaPlanes", str(self.ui.betweenSpinBox.value))
@@ -754,6 +874,14 @@ def updateParameterNodeFromGUI(self, caller=None, event=None):
self._parameterNode.SetParameter("useNonDecimatedBoneModelsForPreview","True")
else:
self._parameterNode.SetParameter("useNonDecimatedBoneModelsForPreview","False")
+ if self.ui.showMandiblePlanesToolButton.checked:
+ self._parameterNode.SetParameter("showMandiblePlanes","True")
+ else:
+ self._parameterNode.SetParameter("showMandiblePlanes","False")
+ if self.ui.showMandiblePlanesInteractionHandlesToolButton.checked:
+ self._parameterNode.SetParameter("showMandiblePlanesInteractionHandles","True")
+ else:
+ self._parameterNode.SetParameter("showMandiblePlanesInteractionHandles","False")
if self.ui.checkSecurityMarginOnMiterBoxCreationCheckBox.checked:
self._parameterNode.SetParameter("checkSecurityMarginOnMiterBoxCreation","True")
else:
@@ -779,6 +907,19 @@ def updateParameterNodeFromGUI(self, caller=None, event=None):
else:
self._parameterNode.SetParameter("makeAllDentalImplanCylindersParallel","False")
+ if self.ui.showFibulaSegmentsLengthsCheckBox.checked:
+ self._parameterNode.SetParameter("showFibulaSegmentsLengths", "True")
+ else:
+ self._parameterNode.SetParameter("showFibulaSegmentsLengths", "False")
+ if self.ui.showOriginalMandibleCheckBox.checked:
+ self._parameterNode.SetParameter("showOriginalMandible", "True")
+ else:
+ self._parameterNode.SetParameter("showOriginalMandible", "False")
+ if self.ui.showBiggerSawBoxesInteractionHandlesCheckBox.checked:
+ self._parameterNode.SetParameter("showBiggerSawBoxesInteractionHandles", "True")
+ else:
+ self._parameterNode.SetParameter("showBiggerSawBoxesInteractionHandles", "False")
+
self._parameterNode.EndModify(wasModified)
def onLightsRenderingComboBox(self, text):
@@ -813,6 +954,25 @@ def onOrientation3DCubeCheckBox(self):
viewNode.SetOrientationMarkerType(slicer.vtkMRMLAbstractViewNode.OrientationMarkerTypeNone)
viewNode.SetOrientationMarkerSize(slicer.vtkMRMLAbstractViewNode.OrientationMarkerSizeMedium)
+ def onEmailBugReportButton(self):
+ send2 = ".".join("bone reconstruction planner+bug report@gmail com".split(" "))
+ self.logic.prepareSendEmailOnWebBrowser(
+ emailVariable = send2,
+ subjectVariable = "[WRITE BUG TITLE]",
+ bodyVariable = "Please describe the bug you found here." + "\n\n" + "Please attach the error log file here."
+ )
+
+ def onEmailFeatureRequestButton(self):
+ send2 = ".".join("bone reconstruction planner+feature request@gmail com".split(" "))
+ self.logic.prepareSendEmailOnWebBrowser(
+ emailVariable = send2,
+ subjectVariable = "[WRITE FEATURE REQUEST TITLE]",
+ bodyVariable = "Please describe the new feature you'd like here."
+ )
+
+ def onOpenDocumentationButton(self):
+ self.logic.openDocumentationOnWebBrowser()
+
def onFixCutGoesThroughTheMandibleTwiceCheckBox(self):
if self._parameterNode is None or self._updatingGUIFromParameterNode:
return
@@ -875,56 +1035,67 @@ def onCenterFibulaLineButton(self):
def onHardVSPUpdateButton(self):
self.logic.hardVSPUpdate()
+
+ def onLockVSPButton(self,checked):
+ self.logic.lockVSP(checked)
- def onShowHideBiggerSawBoxesInteractionHandlesButton(self):
+ def setBiggerSawBoxesInteractionHandlesVisibility(self, visibility):
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)
sawBoxesPlanesFolder = shNode.GetItemByName("sawBoxes Planes")
sawBoxesPlanesList = createListFromFolderID(sawBoxesPlanesFolder)
for i in range(len(sawBoxesPlanesList)):
displayNode = sawBoxesPlanesList[i].GetDisplayNode()
- handlesVisibility = displayNode.GetHandlesInteractive()
- displayNode.SetHandlesInteractive(not handlesVisibility)
+ displayNode.SetHandlesInteractive(visibility)
- def onShowHideMandiblePlanesInteractionHandlesButton(self):
+ def setMandiblePlanesVisibility(self, visibility):
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)
mandibularPlanesFolder = shNode.GetItemByName("Mandibular planes")
mandibularPlanesList = createListFromFolderID(mandibularPlanesFolder)
for i in range(len(mandibularPlanesList)):
displayNode = mandibularPlanesList[i].GetDisplayNode()
- handlesVisibility = displayNode.GetHandlesInteractive()
- displayNode.SetHandlesInteractive(not handlesVisibility)
+ displayNode.SetVisibility(visibility)
- def onShowHideFibulaSegmentsLengthsButton(self):
+ def setMandiblePlanesInteractionHandlesVisibility(self, visibility):
+ shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)
+ mandibularPlanesFolder = shNode.GetItemByName("Mandibular planes")
+ mandibularPlanesList = createListFromFolderID(mandibularPlanesFolder)
+
+ for i in range(len(mandibularPlanesList)):
+ displayNode = mandibularPlanesList[i].GetDisplayNode()
+ displayNode.SetHandlesInteractive(visibility)
+
+ def setFibulaSegmentsLengthsVisibility(self, visibility):
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)
fibulaSegmentsLengthsFolder = shNode.GetItemByName("Fibula Segments Lengths")
fibulaSegmentsLengthsList = createListFromFolderID(fibulaSegmentsLengthsFolder)
for i in range(len(fibulaSegmentsLengthsList)):
lineDisplayNode = fibulaSegmentsLengthsList[i].GetDisplayNode()
- visibility = lineDisplayNode.GetVisibility()
- lineDisplayNode.SetVisibility(not visibility)
+ lineDisplayNode.SetVisibility(visibility)
def onCreate3DModelOfTheReconstructionButton(self):
self.logic.create3DModelOfTheReconstruction()
- def onShowHideOriginalMandibleButton(self):
+ def setOriginalMandibleVisility(self, visibility):
mandibleModelNode = self._parameterNode.GetNodeReference("mandibleModelNode")
decimatedMandibleModelNode = self._parameterNode.GetNodeReference("decimatedMandibleModelNode")
+
+ if (mandibleModelNode is None) and (decimatedMandibleModelNode) is None:
+ return
+
useNonDecimatedBoneModelsForPreviewChecked = self._parameterNode.GetParameter("useNonDecimatedBoneModelsForPreview") == "True"
mandibleModelDisplayNode = mandibleModelNode.GetDisplayNode()
decimatedMandibleModelDisplayNode = decimatedMandibleModelNode.GetDisplayNode()
- if mandibleModelDisplayNode.GetVisibility() or decimatedMandibleModelDisplayNode.GetVisibility():
- mandibleModelDisplayNode.SetVisibility(False)
+ if useNonDecimatedBoneModelsForPreviewChecked:
+ mandibleModelDisplayNode.SetVisibility(visibility)
decimatedMandibleModelDisplayNode.SetVisibility(False)
else:
- if useNonDecimatedBoneModelsForPreviewChecked:
- mandibleModelDisplayNode.SetVisibility(True)
- else:
- decimatedMandibleModelDisplayNode.SetVisibility(True)
+ decimatedMandibleModelDisplayNode.SetVisibility(visibility)
+ mandibleModelDisplayNode.SetVisibility(False)
def onUpdateFibulaDentalImplantCylindersButton(self):
self.logic.onUpdateFibuladentalImplantsTimerTimeout()
@@ -981,6 +1152,18 @@ def setDefaultParameters(self, parameterNode):
parameterNode.SetParameter("kindOfMandibleResection","Segmental Mandibulectomy")
if not parameterNode.GetParameter("mandibleSideToRemove"):
parameterNode.SetParameter("mandibleSideToRemove","Removing right side")
+ if not parameterNode.GetParameter("showFibulaSegmentsLengths"):
+ parameterNode.SetParameter("showFibulaSegmentsLengths","True")
+ if not parameterNode.GetParameter("showOriginalMandible"):
+ parameterNode.SetParameter("showOriginalMandible","True")
+ if not parameterNode.GetParameter("showBiggerSawBoxesInteractionHandles"):
+ parameterNode.SetParameter("showBiggerSawBoxesInteractionHandles","False")
+ if not parameterNode.GetParameter("showMandiblePlanes"):
+ parameterNode.SetParameter("showMandiblePlanes","True")
+ if not parameterNode.GetParameter("showMandiblePlanesInteractionHandles"):
+ parameterNode.SetParameter("showMandiblePlanesInteractionHandles","True")
+ if not parameterNode.GetParameter("lockVSP"):
+ parameterNode.SetParameter("lockVSP","False")
def getParentFolderItemID(self):
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)
@@ -1028,6 +1211,30 @@ def getMandiblePlanesFolderItemID(self):
else:
return shNode.CreateFolderItem(self.getParentFolderItemID(),"Mandibular planes")
+ def prepareSendEmailOnWebBrowser(self, emailVariable, subjectVariable, bodyVariable, ccVariable="", bccVariable=""):
+ parsedBodyVariable = bodyVariable.replace(" ", "%20").replace("\n", "%0D%0A")
+ #
+ prepareEmailString = (
+ f'mailto:{emailVariable}?'
+ f'subject={subjectVariable}&'
+ f'body={parsedBodyVariable}'
+ )
+ #
+ if ccVariable != "":
+ prepareEmailString += f'&cc={ccVariable}'
+ #
+ if bccVariable != "":
+ prepareEmailString += f'&bcc={bccVariable}'
+ #
+ prepareEmailUrl = qt.QUrl(prepareEmailString)
+ #
+ # Open email client
+ qt.QDesktopServices.openUrl(prepareEmailUrl)
+
+ def openDocumentationOnWebBrowser(self):
+ documentationUrl = qt.QUrl("https://github.com/SlicerIGT/SlicerBoneReconstructionPlanner#table-of-contents")
+ qt.QDesktopServices.openUrl(documentationUrl)
+
def addMandibularCurve(self):
curveNode = slicer.mrmlScene.CreateNodeByClass("vtkMRMLMarkupsCurveNode")
curveNode.SetName("temp")
@@ -1172,6 +1379,12 @@ def onPlaneModifiedTimer(self,sourceNode,event):
self.generateFibulaPlanesTimer.start()
def onGenerateFibulaPlanesTimerTimeout(self):
+ parameterNode = self.getParameterNode()
+ lockVSPChecked = parameterNode.GetParameter("lockVSP") == "True"
+ if lockVSPChecked:
+ logging.info('VSP updates are locked. Please set "lockVSP" parameter to "False".')
+ return
+
import time
startTime = time.time()
logging.info('Processing started')
@@ -1302,7 +1515,8 @@ def setInteractiveHandlesVisibilityOfMarkups(self,markupsList,visibility):
def setMarkupsListLocked(self,markupsList,locked):
for i in range(len(markupsList)):
- markupsList[i].SetLocked(locked)
+ if markupsList[i] is not None:
+ markupsList[i].SetLocked(locked)
def addMandiblePlaneObservers(self):
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)
@@ -1314,14 +1528,10 @@ def addMandiblePlaneObservers(self):
self.mandiblePlaneObserversAndNodeIDList.append([observer,mandibularPlanesList[i].GetID()])
def removeMandiblePlaneObservers(self):
- shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)
- mandibularPlanesFolder = shNode.GetItemByName("Mandibular planes")
- mandibularPlanesList = createListFromFolderID(mandibularPlanesFolder)
-
if len(self.mandiblePlaneObserversAndNodeIDList) == 0:
return
- for i in range(len(mandibularPlanesList)):
+ for i in range(len(self.mandiblePlaneObserversAndNodeIDList)):
mandiblePlane = slicer.mrmlScene.GetNodeByID(self.mandiblePlaneObserversAndNodeIDList[i][1])
mandiblePlane.RemoveObserver(self.mandiblePlaneObserversAndNodeIDList[i][0])
self.mandiblePlaneObserversAndNodeIDList = []
@@ -2080,6 +2290,10 @@ def hardVSPUpdate(self):
self.resetPlan()
self.onGenerateFibulaPlanesTimerTimeout()
+ def lockVSP(self, doLock):
+ parameterNode = self.getParameterNode()
+ parameterNode.SetParameter("lockVSP", str(doLock))
+
def generateFibulaPlanesFibulaBonePiecesAndTransformThemToMandible(self):
parameterNode = self.getParameterNode()
useNonDecimatedBoneModelsForPreviewChecked = parameterNode.GetParameter("useNonDecimatedBoneModelsForPreview") == "True"
@@ -3756,6 +3970,8 @@ def createSawBoxesFromFirstAndLastMandiblePlanes(self):
shNode.RemoveItem(pointsIntersectionsFolder)
self.setRedSliceForBoxModelsDisplayNodes()
+
+ parameterNode.SetParameter("showBiggerSawBoxesInteractionHandles","True")
def onSawBoxPlaneMoved(self,sourceNode,event):
for i in range(len(self.sawBoxPlaneObserversPlaneNodeIDAndTransformIDList)):
@@ -4022,12 +4238,14 @@ def create3DModelOfTheReconstruction(self):
self.exportScaledFibulaPiecesForNeomandibleReconstructionToFolder(scaledFibulaPiecesFolder)
scaledFibulaPiecesList = createListFromFolderID(scaledFibulaPiecesFolder)
- listOfObjectsToUnite = scaledFibulaPiecesList + [resectedMandible]
-
combineModelsLogic = combineModelsRobustLogic
+ listOfObjectsToUnite = scaledFibulaPiecesList + [resectedMandible]
for i in range(len(listOfObjectsToUnite)):
combineModelsLogic.process(mandibleReconstructionModel, listOfObjectsToUnite[i], mandibleReconstructionModel, 'union')
-
+ condylarBeamModel = parameterNode.GetNodeReference("condylarBeamModel")
+ if condylarBeamModel is not None:
+ combineModelsLogic.process(mandibleReconstructionModel, condylarBeamModel, mandibleReconstructionModel, 'union')
+
shNode.RemoveItem(scaledFibulaPiecesFolder)
if mandibleReconstructionModel.GetPolyData().GetNumberOfPoints() == 0:
@@ -4036,7 +4254,7 @@ def create3DModelOfTheReconstruction(self):
return
- def exportScaledFibulaPiecesForNeomandibleReconstructionToFolder(self, scaledFibulaPiecesFolder):
+ def exportScaledFibulaPiecesForNeomandibleReconstructionToFolder(self, scaledFibulaPiecesFolder, scaleFactor=1.001):
shNode = slicer.mrmlScene.GetSubjectHierarchyNode()
planeList = createListFromFolderID(self.getMandiblePlanesFolderItemID())
transformedFibulaPiecesFolder = shNode.GetItemByName("Transformed Fibula Pieces")
@@ -4054,7 +4272,7 @@ def exportScaledFibulaPiecesForNeomandibleReconstructionToFolder(self, scaledFib
scaleTransform.PostMultiply()
scaleTransform.Translate(-origin)
#Just scale them enough so that boolean union is successful
- scaleTransform.Scale(1.0001, 1.0001, 1.0001)
+ scaleTransform.Scale(scaleFactor, scaleFactor, scaleFactor)
scaleTransform.Translate(origin)
scaleTransformer = vtk.vtkTransformPolyDataFilter()
@@ -4940,9 +5158,9 @@ def section_SimulateAndImproveMandibleReconstruction(self):
if not slicer.app.commandOptions().noMainWindow:
# hide original mandible
- self.widgetBRP.onShowHideOriginalMandibleButton()
+ self.widgetBRP.setOriginalMandibleVisility(False)
# hide mandible plane handles
- self.widgetBRP.onShowHideMandiblePlanesInteractionHandlesButton()
+ self.widgetBRP.setMandiblePlanesInteractionHandlesVisibility(False)
self.delayDisplay("Optimize bones contact in reconstruction")
parameterNode = self.logicBRP.getParameterNode()
@@ -5094,9 +5312,9 @@ def section_createAndUpdateSawBoxesFromMandiblePlanes(self):
else:
layoutManager.setMaximizedViewNode(None)
# show mandible plane handles
- self.widgetBRP.onShowHideMandiblePlanesInteractionHandlesButton()
+ self.widgetBRP.setMandiblePlanesInteractionHandlesVisibility(True)
# hide saw boxes handles
- self.widgetBRP.onShowHideBiggerSawBoxesInteractionHandlesButton()
+ self.widgetBRP.setBiggerSawBoxesInteractionHandlesVisibility(False)
# asserts below
diff --git a/BoneReconstructionPlanner/Resources/Icons/axes.svg b/BoneReconstructionPlanner/Resources/Icons/axes.svg
new file mode 100644
index 0000000..2568e4e
--- /dev/null
+++ b/BoneReconstructionPlanner/Resources/Icons/axes.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/BoneReconstructionPlanner/Resources/Icons/bone_48.svg b/BoneReconstructionPlanner/Resources/Icons/bone_48.svg
new file mode 100644
index 0000000..6439252
--- /dev/null
+++ b/BoneReconstructionPlanner/Resources/Icons/bone_48.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/BoneReconstructionPlanner/Resources/Icons/lock_48.svg b/BoneReconstructionPlanner/Resources/Icons/lock_48.svg
new file mode 100644
index 0000000..4c1938b
--- /dev/null
+++ b/BoneReconstructionPlanner/Resources/Icons/lock_48.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/BoneReconstructionPlanner/Resources/Icons/mail_48.svg b/BoneReconstructionPlanner/Resources/Icons/mail_48.svg
new file mode 100644
index 0000000..dc7b127
--- /dev/null
+++ b/BoneReconstructionPlanner/Resources/Icons/mail_48.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/BoneReconstructionPlanner/Resources/Icons/quick_reference_48.svg b/BoneReconstructionPlanner/Resources/Icons/quick_reference_48.svg
new file mode 100644
index 0000000..399f550
--- /dev/null
+++ b/BoneReconstructionPlanner/Resources/Icons/quick_reference_48.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/BoneReconstructionPlanner/Resources/Icons/target_48.svg b/BoneReconstructionPlanner/Resources/Icons/target_48.svg
new file mode 100644
index 0000000..6164d97
--- /dev/null
+++ b/BoneReconstructionPlanner/Resources/Icons/target_48.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/BoneReconstructionPlanner/Resources/Icons/update_48.svg b/BoneReconstructionPlanner/Resources/Icons/update_48.svg
new file mode 100644
index 0000000..98ef786
--- /dev/null
+++ b/BoneReconstructionPlanner/Resources/Icons/update_48.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/BoneReconstructionPlanner/Resources/Icons/visibility_48.svg b/BoneReconstructionPlanner/Resources/Icons/visibility_48.svg
new file mode 100644
index 0000000..704e443
--- /dev/null
+++ b/BoneReconstructionPlanner/Resources/Icons/visibility_48.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/BoneReconstructionPlanner/Resources/Pictures/screenshotNicerRendering.png b/BoneReconstructionPlanner/Resources/Pictures/screenshotNicerRendering.png
new file mode 100644
index 0000000..441eb3a
Binary files /dev/null and b/BoneReconstructionPlanner/Resources/Pictures/screenshotNicerRendering.png differ
diff --git a/BoneReconstructionPlanner/Resources/UI/BoneReconstructionPlanner.ui b/BoneReconstructionPlanner/Resources/UI/BoneReconstructionPlanner.ui
index 56fcf87..91f6406 100644
--- a/BoneReconstructionPlanner/Resources/UI/BoneReconstructionPlanner.ui
+++ b/BoneReconstructionPlanner/Resources/UI/BoneReconstructionPlanner.ui
@@ -6,11 +6,61 @@
0
0
- 642
- 3327
+ 664
+ 3768
+ -
+
+
+
+ 75
+ true
+
+
+
+ versionLabel
+
+
+
+ -
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ Tell us if something does not work as expected
+
+
+ Bug report
+
+
+
+ -
+
+
+ Tell us how we can improve the software
+
+
+ Feature request
+
+
+
+
+
+ -
+
+
+ Open documentation
+
+
+
-
@@ -18,489 +68,691 @@
-
-
-
- Qt::AlignCenter
-
-
- 0
-
-
-
-
-
- Current Scalar Volume
-
-
-
- -
-
-
- true
-
-
-
- vtkMRMLScalarVolumeNode
-
-
-
- false
-
-
- false
-
-
-
- -
-
-
- Right side leg
-
-
-
- -
-
-
- (tick if fibula is the one from the right leg)
-
-
- false
-
-
-
- -
-
-
- Select mandibular segmentation
-
-
-
- -
-
-
- true
-
-
-
- vtkMRMLSegmentationNode
-
-
-
- false
-
-
- false
-
-
-
- -
-
-
- Select fibula segmentation
-
-
-
- -
-
-
- true
-
-
-
- vtkMRMLSegmentationNode
-
-
-
- false
-
-
- false
-
-
-
- -
-
-
- Place mandibular curve
-
-
- Qt::AlignCenter
-
-
-
- -
-
-
- Add mandibular curve
-
-
-
- -
-
-
- Place fibula line
-
-
- Qt::AlignCenter
-
-
-
- -
-
-
- Add fibula line
-
-
-
- -
-
-
- Place mandibular planes
-
-
-
- -
-
-
- Add cut plane
-
-
-
- -
-
-
- Initial space (mm)
-
-
-
- -
-
-
- 1
-
-
-
- -
-
-
- Intersection distance multiplier
-
-
-
- -
-
-
- 1
-
-
- 30.000000000000000
-
-
- 0.100000000000000
-
-
- 1.000000000000000
-
-
-
- -
-
-
- Between space (mm)
-
-
-
- -
-
-
- 1
-
-
- 1.000000000000000
-
-
-
- -
-
-
- Security margin (mm)
-
-
-
- -
-
-
- 1
-
-
- 30.000000000000000
-
-
- 0.100000000000000
-
-
- 1.000000000000000
-
-
-
- -
-
-
- Select mandible curve
-
-
-
- -
-
-
- true
-
-
-
- vtkMRMLMarkupsCurveNode
-
-
-
- false
-
-
- false
-
-
-
- -
-
-
- Select fibula line
-
-
-
- -
-
-
- true
-
-
-
- vtkMRMLMarkupsLineNode
-
-
-
- false
-
-
- false
-
-
-
-
-
- -
-
-
- Create bone models from segmentations
-
-
-
- -
-
-
- Center fibula line using fibula model
-
-
-
- -
-
-
- Show/Hide original mandible model
+
+
+
+ 0
+ 0
+
-
-
- -
-
-
- 0
+
+ QFrame::NoFrame
-
- 0
+
+ QFrame::Plain
-
-
-
-
-
- 3
- 0
-
-
-
- Show/Hide fibula segments lengths
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Fibula segment measurement mode. Needs update
-
-
- QComboBox::AdjustToMinimumContentsLength
-
-
-
-
- center2center
+
+
-
+
+
+ 0
+
+
+ 0
-
- -
+
-
+
+
+ Right side leg
+
+
+
+ -
+
+
+ (tick if fibula is the one from the right leg)
+
+
+ false
+
+
+
+ -
+
+
+ Select mandibular segmentation
+
+
+
+ -
+
+
+ true
+
+
+
+ vtkMRMLSegmentationNode
+
+
+
+ false
+
+
+ false
+
+
+
+ -
+
+
+ Select fibula segmentation
+
+
+
+ -
+
+
+ true
+
+
+
+ vtkMRMLSegmentationNode
+
+
+
+ false
+
+
+ false
+
+
+
+ -
+
+
+ Place mandibular curve
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ Add mandibular curve
+
+
+
+ -
+
+
+ Place fibula line
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ Add fibula line
+
+
+
+ -
+
+
+ Place mandibular planes
+
+
+
+ -
+
+
+ Add cut plane
+
+
+
+ -
+
+
+ Initial space (mm)
+
+
+
+ -
+
+
+ 1
+
+
+
+ -
+
+
+ Intersection distance multiplier
+
+
+
+ -
+
+
+ 1
+
+
+ 30.000000000000000
+
+
+ 0.100000000000000
+
+
+ 1.000000000000000
+
+
+
+ -
+
+
+ Between space (mm)
+
+
+
+ -
+
+
+ 1
+
+
+ 1.500000000000000
+
+
+
+ -
+
+
+ Select mandible curve
+
+
+
+ -
+
+
+ true
+
+
+
+ vtkMRMLMarkupsCurveNode
+
+
+
+ false
+
+
+ false
+
+
+
+ -
+
+
+ Select fibula line
+
+
+
+ -
+
+
+ true
+
+
+
+ vtkMRMLMarkupsLineNode
+
+
+
+ false
+
+
+ false
+
+
+
+
+
+ -
+
- proximal2proximal
+ Create bone models from segmentations
-
- -
+
+
+ -
+
- distal2distal
+ Center fibula line using fibula model
-
-
-
-
-
- -
-
-
- 0
-
-
- 0
-
-
-
-
-
-
+
+
+ -
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ Fibula segment lengths measurements
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Fibula segment measurement mode. Needs update
+
+
+ QComboBox::AdjustToMinimumContentsLength
+
+
-
+
+ center2center
+
+
+ -
+
+ proximal2proximal
+
+
+ -
+
+ distal2distal
+
+
+
+
+
+
+ -
+
+
+ 0
+
+
+ 0
+
+
-
+
+
-
+
+ Segmental Mandibulectomy
+
+
+ -
+
+ Hemimandibulectomy
+
+
+
+
+ -
+
+
-
+
+ Removing right side
+
+
+ -
+
+ Removing left side
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
- Segmental Mandibulectomy
+ Automatic mandibular planes positioning for maximum bones contact area
-
- -
+
+
+ -
+
- Hemimandibulectomy
+ Make all mandible planes rotate together
+
+
+
+ -
+
+
+ Use when you have finished editing
-
-
-
- -
-
-
-
- Removing right side
+ Use more exact version of the positioning algorithm (takes more time)
-
- -
+
+
+ -
+
- Removing left side
+ Use non-decimated bone models for preview (takes more time)
-
- -
+
+
+ -
+
-
+ Fix cut goes through the mandible twice
-
-
-
-
-
- -
-
-
- Automatic mandibular planes positioning for maximum bones contact area
-
+
+
+
-
-
-
- Make all mandible planes rotate together
+
+
+ QLayout::SetMaximumSize
-
-
- -
-
-
- Use when you have finished editing
-
-
- Use more exact version of the positioning algorithm (takes more time)
-
-
-
- -
-
-
- Use non-decimated bone models for preview (takes more time)
-
-
-
- -
-
-
- Fix cut goes through the mandible twice
-
-
-
- -
-
0
0
-
-
-
-
- true
-
-
-
- 3
- 0
-
-
-
- Update fibula planes over fibula line; update fibula bone pieces
-and transform them to mandible
-
+
-
+
+
+
+ 0
+ 0
+
+
+
+ QFrame::NoFrame
+
+
+ QFrame::Plain
+
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Hard-update (use when normal update does not work correctly)
+
+
+ true
+
+
+
+
+
+
+
- -
-
-
-
- 0
- 0
-
-
-
- Hard-update (use when normal update does not work correctly)
-
-
- true
-
-
-
-
+
-
+
+
+ QFrame::NoFrame
+
+
+ QFrame::Plain
+
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Lock updates to VSP
+
+
+
+
+
+ true
+
+
+
+
-
-
-
- Create 3D model of the reconstruction for 3D printing
+
+
+ QFrame::NoFrame
+
+ QFrame::Plain
+
+
+
-
+
+
+ Create 3D model of the reconstruction for
+3D printing
+
+
+
+ -
+
+
+
+ vtkMRMLModelNode
+
+
+
+
+
+
+ true
+
+
+ false
+
+
+ false
+
+
+ Singleton
+
+
+ No inter-condylar beam
+
+
+
+
+
+
+ -
+
+
+
+ 100
+ 135
+
+
+
+ QFrame::NoFrame
+
+
+ QFrame::Plain
+
+
+
+ 7
+
+
-
+
+
+ 7
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ Current Scalar Volume
+
+
+
+ -
+
+
+ true
+
+
+
+ vtkMRMLScalarVolumeNode
+
+
+
+ false
+
+
+ false
+
+
+
+
+
+ -
+
+
+ QLayout::SetMaximumSize
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Mandible planes
+
+
+
+ -
+
+
+
+
+
+ true
+
+
+
+ -
+
+
+
+
+
+ true
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+ -
+
+
+ Show original mandible model
+
+
+
+ -
+
+
+ Show fibula segments lengths
+
+
+
+ -
+
+
+ 0
+
+
-
+
+
+ Lights rendering:
+
+
+
+ -
+
+
-
+
+ Lamp
+
+
+ -
+
+ Lamp and Shadows
+
+
+ -
+
+ MultiLamp
+
+
+ -
+
+ MultiLamp and Shadows
+
+
+
+
+
+
+
-
@@ -846,19 +1098,69 @@ and transform them to mandible
+ -
+
+
+ QFrame::NoFrame
+
+
+ QFrame::Plain
+
+
+ 1
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ Security margin (mm)
+
+
+
+ -
+
+
+ 1
+
+
+ 30.000000000000000
+
+
+ 0.100000000000000
+
+
+ 1.000000000000000
+
+
+
+
+
+
-
0
-
-
+
-
Slot width (mm)
- -
+
-
1
@@ -874,14 +1176,14 @@ and transform them to mandible
- -
+
-
Slot length (mm)
- -
+
-
1
@@ -894,14 +1196,14 @@ and transform them to mandible
- -
+
-
Slot wall (mm)
- -
+
-
1
@@ -917,14 +1219,14 @@ and transform them to mandible
- -
+
-
Slot height (mm)
- -
+
-
1
@@ -937,14 +1239,14 @@ and transform them to mandible
- -
+
-
Bigger miter box distance to fibula (mm)
- -
+
-
1
@@ -960,14 +1262,14 @@ and transform them to mandible
- -
+
-
Miter box direction line
- -
+
-
false
@@ -1104,16 +1406,9 @@ and transform them to mandible
-
-
+
- Show/Hide biggerSawBoxes interaction handles
-
-
-
- -
-
-
- Show/Hide mandible planes interaction handles
+ Show biggerSawBoxes interaction handles
@@ -1400,7 +1695,7 @@ p, li { white-space: pre-wrap; }
0.010000000000000
- 0.200000000000000
+ 0.250000000000000
@@ -1434,37 +1729,6 @@ Desing
- -
-
-
- Lights rendering:
-
-
-
- -
-
-
-
-
- Lamp
-
-
- -
-
- Lamp and Shadows
-
-
- -
-
- MultiLamp
-
-
- -
-
- MultiLamp and Shadows
-
-
-
-
@@ -1737,5 +2001,21 @@ Desing
+
+ BoneReconstructionPlanner
+ mrmlSceneChanged(vtkMRMLScene*)
+ condylarBeamModelSelector
+ setMRMLScene(vtkMRMLScene*)
+
+
+ 342
+ 1782
+
+
+ 544
+ 986
+
+
+
diff --git a/README.md b/README.md
index 5e1a322..59df7b4 100644
--- a/README.md
+++ b/README.md
@@ -54,7 +54,7 @@ A [3D Slicer](https://www.slicer.org/) extension for virtual surgical planning o
# Developer statement
-As an open-source developer committed to quality, I am dedicated to ensuring that our "medical" software meets the highest standards, with the goal of achieving ISO 13485 compliance in the future. If you encounter any bugs or issues, please report them, and I will work diligently to address and resolve them promptly. (This software is not FDA approved)
+As an open-source developer committed to quality, I am dedicated to ensuring that our "medical" software meets the highest standards, with the goal of achieving ISO 13485 compliance in the future. If you encounter any bugs or issues, please [report them](https://github.com/SlicerIGT/SlicerBoneReconstructionPlanner/issues/new), and I will work diligently to address and resolve them promptly. (This software is not FDA approved)
# Citations
@@ -78,6 +78,7 @@ https://www.sciencedirect.com/science/article/pii/S2666964123000103
# Table of Contents
- [Overview](#bonereconstructionplanner)
+- [Developer statement](#developer-statement)
- [Citations](#citations)
- [Description](#description)
- [Benefits](#benefits)
@@ -90,6 +91,7 @@ https://www.sciencedirect.com/science/article/pii/S2666964123000103
- [Sample Data](#sample-data)
- [Instructions](#instructions)
- [Installing BoneReconstructionPlanner](#installing-bonereconstructionplanner)
+ - [Saving the scene](#saving-the-scene)
- [Segmentation (Preparation for Virtual Surgical Planning)](#segmentation-preparation-for-virtual-surgical-planning)
- [Virtual Surgical Planning](#virtual-surgical-planning)
- [Personalized Fibula Guide Generation](#personalized-fibula-guide-generation)
@@ -97,9 +99,10 @@ https://www.sciencedirect.com/science/article/pii/S2666964123000103
- [Finish the Fibula Surgical Guide](#finish-the-fibula-surgical-guide)
- [Personalized Mandible Surgical Guide](#personalized-mandible-surgical-guide)
- [Mandible Reconstruction Simulation](#mandible-reconstruction-simulation)
-- [Contributing](#Contributing)
-- [Community](#Community)
-- [Additional Resources](#Additional-Resources)
+ - [Export the planning outputs](#export-the-planning-outputs)
+ - [Settings](#settings)
+- [User contact and feedback](#user-contact-and-feedback)
+ - [Contact](#contact)
- [License](#license)
# Description
@@ -214,6 +217,10 @@ See more than 40 plans of other users:
To have in mind: every once in a while, you can enter the extension manager and check for updates of this extension to get latest bug fixes and added features
+## Saving the scene
+- [Save](https://slicer.readthedocs.io/en/latest/user_guide/data_loading_and_saving.html#save-data) frequently as the surgical plan can be reopened from where you left it if there is a crash (software malfunction). We suggest using the "Save scene as single file (.mrb file format)", then you can save your progress with different names "example_plan_v01.mrb", "example_plan_v02.mrb", etc
+
+
## Segmentation (Preparation for Virtual Surgical Planning)
Make a mandible segmentation and a fibula segmentation.
@@ -234,7 +241,6 @@ You'll have to do the same for mandible in another segmentation node.
## Virtual Surgical Planning
-0. [Save](https://slicer.readthedocs.io/en/latest/user_guide/data_loading_and_saving.html#non-dicom-data) frequently as the surgical plan can be reopened from where you left it if there is a crash (software malfunction). We are trying to fix a bug that makes Slicer close unexpectedly during boolean operations ([more info here](https://github.com/SlicerIGT/SlicerBoneReconstructionPlanner/issues/118)) if you find any other problem please [report it here](https://github.com/SlicerIGT/SlicerBoneReconstructionPlanner/issues/new).
1. Click the search icon on the left of the module selector and write 'BoneReconstructionPlanner'. Click "Switch to module".
2. If fibula is the one from the right leg tick "Right side leg" checkbox. This makes fibula coordinate system X axis be always medial independently of the which leg is used to harvest the fibula.
3. Select the mandibular segmentation and the fibula segmentation.
@@ -295,16 +301,25 @@ Except that:
- You need to put the correct models on the corresponding selectors on "Mandible Surgical Guide Generation" panel
## Mandible Reconstruction Simulation
+This maybe useful for users that want to prebend plates with a 3D printed model.
1. Do a [Virtual Surgical Plan](#virtual-surgical-planning)
-2. Click "Create 3D model of the reconstruction for 3D printing". This button maybe useful for users that want to prebend plates with a 3D printed model.
+2. Optionally, you can add an inter-condylar beam (i.e. a tube model) to the reconstruction. You can create the tube easily from a markups line with points on the condyles using the "Markups To Model" module.
+3. Click "Create 3D model of the reconstruction for 3D printing".
+
+## Export the planning outputs
+- You may want to [export](https://slicer.readthedocs.io/en/latest/user_guide/data_loading_and_saving.html#export-data) the 3D models you created of mandible and fibula custom surgical guides, and the neomandible. Remember to select the ".stl" export format (which is the format used for 3D printers).
## Settings
You can use the "Lights rendering" setting to make the 3D visualizations nicer. Try "MultiLamp and Shadows", if you don't like it, you can always go back to "Lamp" default setting.
+
-# User contributions and feedback
+# User contact and feedback
Fell free to open an [issue](https://github.com/SlicerIGT/SlicerBoneReconstructionPlanner/issues/new) (or [report here](https://discourse.slicer.org/t/how-to-design-3d-printed-surgical-guide-for-mandible-reconstruction/19754/11)) if you find the instructions or the videotutorial inaccurate, or if you need help finishing the workflow
+## Contact
+_bone (dot) reconstruction (dot) planner (at) gmail (dot) com_
+
# License
- Read license