diff --git a/.github/release.yml b/.github/release.yml
new file mode 100644
index 000000000..9f3a07680
--- /dev/null
+++ b/.github/release.yml
@@ -0,0 +1,13 @@
+# .github/release.yml
+
+changelog:
+ categories:
+ - title: Features
+ labels:
+ - '*'
+ exclude:
+ labels:
+ - dependencies
+ - title: Dependencies
+ labels:
+ - dependencies
diff --git a/.github/workflows/maven-build-all-installer.yml b/.github/workflows/build-installers.yml
similarity index 75%
rename from .github/workflows/maven-build-all-installer.yml
rename to .github/workflows/build-installers.yml
index 9732a97ae..10264c6e9 100644
--- a/.github/workflows/maven-build-all-installer.yml
+++ b/.github/workflows/build-installers.yml
@@ -1,15 +1,12 @@
# This workflow will build a Java project with Maven
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
-name: Build All Installers
+name: Build Installers
on:
- push:
- branches: [ master ]
+ workflow_call:
pull_request:
- branches: [ master ]
- workflow_dispatch:
- branches: [ master ]
+ branches: [master]
env:
DEV_IDENTITY: BXPZTQZ35S # Your Apple Dev identity, something like BXPZTQZ35S
@@ -21,9 +18,14 @@ jobs:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest, macos-14]
runs-on: ${{ matrix.os }}
- env:
- RELEASE_INSTALLERS: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+ - name: Set Version
+ shell: bash
+ run: |
+ short_version=`git rev-parse --short HEAD`
+ echo "VERSION=$short_version" >> $GITHUB_ENV
- name: Download Wix
uses: i3h/download-release-asset@v1
if: matrix.os == 'windows-latest'
@@ -42,7 +44,7 @@ jobs:
run: echo "$HOME/target/wix" >> $GITHUB_PATH
if: matrix.os == 'windows-latest'
- uses: actions/checkout@v2
- - name: Set up JDK 17
+ - name: Set up JDK
uses: actions/setup-java@v2
with:
java-version: 18.0.2
@@ -58,14 +60,11 @@ jobs:
MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }}
if: ${{ env.MACOS_CERTIFICATE == null && (matrix.os == 'macos-latest' || matrix.os == 'macos-14') }}
run: mvn -B clean install -DskipTests -Pbuild-installer "-Dmatrix.os=${{ matrix.os }}" --file pom.xml
- - name: Update Automatic Release
- uses: marvinpinto/action-automatic-releases@latest
- if: ${{ env.RELEASE_INSTALLERS }}
+
+ - name: Upload Installers
+ uses: actions/upload-artifact@v4
with:
- repo_token: "${{ secrets.GITHUB_TOKEN}}"
- automatic_release_tag: ${{ matrix.os }}
- prerelease: true
- title: ${{ matrix.os }} Development Build
- files: |
+ name: Paintera-${{ matrix.os }}-${{ env.VERSION }}
+ path: |
${{ env.DMG_PATH }}
- ./target/installer-${{ matrix.os }}/*
+ ./target/installer-${{ matrix.os }}/*
\ No newline at end of file
diff --git a/.github/workflows/publish-installers.yml b/.github/workflows/publish-installers.yml
new file mode 100644
index 000000000..6093fae3e
--- /dev/null
+++ b/.github/workflows/publish-installers.yml
@@ -0,0 +1,50 @@
+# This workflow will build a Java project with Maven
+# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
+
+name: Publish Installers
+
+on:
+ push:
+ tags: "paintera-*.*.*"
+ workflow_dispatch:
+
+
+
+env:
+ DEV_IDENTITY: BXPZTQZ35S # Your Apple Dev identity, something like BXPZTQZ35S
+ PRIMARY_BUNDLE_ID: org.janelia.saalfeldlab.Paintera # Unique to your app, often the launcher class
+
+jobs:
+ build_installers:
+ name: Build Installers
+ uses: ./.github/workflows/build-installers.yml
+
+ create_release:
+ needs: build_installers
+ runs-on: ubuntu-latest
+ steps:
+ - name: Download Artifacts
+ uses: actions/download-artifact@v4
+ - name: Set Version
+ run: |
+ tag_name=$(echo ${{ github.ref }} | grep -oE "[^/]+$")
+ echo "VERSION=$tag_name" >> $GITHUB_ENV
+ - name: Display structure of downloaded files
+ run: ls -R
+ - name: Rename Artifacts
+ run: |
+ mv Paintera-windows-latest-*/*.msi Paintera-${{ env.VERSION }}-Windows.msi
+ mv Paintera-ubuntu-latest-*/*.deb Paintera-${{ env.VERSION }}_x86_64.deb
+ mv Paintera-macos-latest-*/*.dmg Paintera-${{ env.VERSION }}-MacOS.dmg
+ mv Paintera-macos-14-*/*.dmg Paintera-${{ env.VERSION }}-MacOS-AppleSilicon.dmg
+ - name: Create Release
+ uses: softprops/action-gh-release@v2
+ with:
+ name: Paintera ${{ env.VERSION }}
+ tag_name: ${{ github.ref }}
+ prerelease: false
+ files: |
+ Paintera-${{ env.VERSION }}-Windows.msi
+ Paintera-${{ env.VERSION }}_x86_64.deb
+ Paintera-${{ env.VERSION }}-MacOS.dmg
+ Paintera-${{ env.VERSION }}-MacOS-AppleSilicon.dmg
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 99a63c6e0..c349f5254 100644
--- a/pom.xml
+++ b/pom.xml
@@ -10,7 +10,7 @@
org.janelia.saalfeldlab
paintera
- 1.1.1-SNAPSHOT
+ 1.2.0-SNAPSHOT
Paintera
New Era Painting and annotation tool
@@ -53,7 +53,7 @@
true
${javadoc.skip}
- 1.2.0
+ 1.3.0
3.0.7
1.4.0
diff --git a/src/main/kotlin/org/janelia/saalfeldlab/fx/ui/ActionBar.kt b/src/main/kotlin/org/janelia/saalfeldlab/fx/ui/ActionBar.kt
index d52666c0e..a254883b1 100644
--- a/src/main/kotlin/org/janelia/saalfeldlab/fx/ui/ActionBar.kt
+++ b/src/main/kotlin/org/janelia/saalfeldlab/fx/ui/ActionBar.kt
@@ -25,7 +25,7 @@ class ActionBar : HBox() {
var newGroup : ToggleGroup? = null
buttons.forEach { node ->
(node as? Toggle)?.apply {
- toggleGroup = toggleGroup ?: ToggleGroup().also { newGroup = it }
+ toggleGroup = toggleGroup ?: newGroup ?: ToggleGroup().also { newGroup = it }
}
}
toggleGroup = newGroup
@@ -77,7 +77,7 @@ class ActionBar : HBox() {
fun List.toolBarNodes() = map { item ->
item.toolBarButton.apply {
- this.onAction ?: let {
+ onAction ?: let {
userData = item
}
isFocusTraversable = false
diff --git a/src/main/kotlin/org/janelia/saalfeldlab/paintera/BindingKeys.kt b/src/main/kotlin/org/janelia/saalfeldlab/paintera/BindingKeys.kt
index 6f281366d..767f79611 100644
--- a/src/main/kotlin/org/janelia/saalfeldlab/paintera/BindingKeys.kt
+++ b/src/main/kotlin/org/janelia/saalfeldlab/paintera/BindingKeys.kt
@@ -103,36 +103,37 @@ private class LateInitNamedKeyCombination(keyCombination: KeyCombination, initNa
}
enum class LabelSourceStateKeys(lateInitNamedKeyCombo : LateInitNamedKeyCombination) : NamedKeyBinding by lateInitNamedKeyCombo {
- SELECT_ALL ( CONTROL_DOWN + A),
- SELECT_ALL_IN_CURRENT_VIEW ( CONTROL_DOWN + SHIFT_DOWN + A),
- LOCK_SEGMENT ( L),
- NEXT_ID ( N),
- COMMIT_DIALOG ( C + CONTROL_DOWN),
- MERGE_ALL_SELECTED ( ENTER + CONTROL_DOWN),
- ARGB_STREAM__INCREMENT_SEED ( C),
- ARGB_STREAM__DECREMENT_SEED ( C + SHIFT_DOWN),
- REFRESH_MESHES ( R),
- CANCEL ( ESCAPE, "cancel tool / exit mode"),
- TOGGLE_NON_SELECTED_LABELS_VISIBILITY ( V + SHIFT_DOWN, "toggle non-selected labels visibility"),
- SEGMENT_ANYTHING__TOGGLE_MODE ( A),
- PAINT_BRUSH ( SPACE),
- FILL_2D ( F),
- FILL_3D ( SHIFT_DOWN + F),
- CLEAR_CANVAS ( CONTROL_DOWN + SHIFT_DOWN + C),
- INTERSECT_UNDERLYING_LABEL ( SHIFT_DOWN + R),
- SHAPE_INTERPOLATION__TOGGLE_MODE ( S),
- SHAPE_INTERPOLATION__TOGGLE_PREVIEW ( CONTROL_DOWN + P),
- SHAPE_INTERPOLATION__ACCEPT_INTERPOLATION ( ENTER),
- SHAPE_INTERPOLATION__SELECT_FIRST_SLICE ( SHIFT_DOWN + LEFT),
- SHAPE_INTERPOLATION__SELECT_LAST_SLICE ( SHIFT_DOWN + RIGHT),
- SHAPE_INTERPOLATION__SELECT_PREVIOUS_SLICE ( LEFT),
- SHAPE_INTERPOLATION__SELECT_NEXT_SLICE ( RIGHT ),
- SHAPE_INTERPOLATION__REMOVE_SLICE_1 ( DELETE, "delete current slice "),
- SHAPE_INTERPOLATION__REMOVE_SLICE_2 ( BACK_SPACE, "delete current slice "),
- SHAPE_INTERPOLATION__AUTO_SAM__NEW_SLICE_LEFT ( OPEN_BRACKET, "shape interpolation: auto SAM: new slice left" ),
- SHAPE_INTERPOLATION__AUTO_SAM__NEW_SLICES_BISECT ( QUOTE, "shape interpolation: auto SAM: new bisect slices" ),
- SHAPE_INTERPOLATION__AUTO_SAM__NEW_SLICE_RIGHT ( CLOSE_BRACKET, "shape interpolation: auto SAM: new slice right" ),
- SHAPE_INTERPOLATION__AUTO_SAM__NEW_SLICE_HERE ( SHIFT_DOWN + A, "shape interpolation: auto SAM: new slice at current location" ),
+ SELECT_ALL ( CONTROL_DOWN + A),
+ SELECT_ALL_IN_CURRENT_VIEW ( CONTROL_DOWN + SHIFT_DOWN + A),
+ LOCK_SEGMENT ( L),
+ NEXT_ID ( N),
+ COMMIT_DIALOG ( C + CONTROL_DOWN),
+ MERGE_ALL_SELECTED ( ENTER + CONTROL_DOWN),
+ ARGB_STREAM__INCREMENT_SEED ( C),
+ ARGB_STREAM__DECREMENT_SEED ( C + SHIFT_DOWN),
+ REFRESH_MESHES ( R),
+ CANCEL ( ESCAPE, "cancel tool / exit mode"),
+ TOGGLE_NON_SELECTED_LABELS_VISIBILITY ( V + SHIFT_DOWN, "toggle non-selected labels visibility"),
+ SEGMENT_ANYTHING__TOGGLE_MODE ( A),
+ PAINT_BRUSH ( SPACE),
+ FILL_2D ( F),
+ FILL_3D ( SHIFT_DOWN + F),
+ CLEAR_CANVAS ( CONTROL_DOWN + SHIFT_DOWN + C),
+ INTERSECT_UNDERLYING_LABEL ( SHIFT_DOWN + R),
+ SHAPE_INTERPOLATION__TOGGLE_MODE ( S),
+ SHAPE_INTERPOLATION__TOGGLE_PREVIEW ( CONTROL_DOWN + P),
+ SHAPE_INTERPOLATION__ACCEPT_INTERPOLATION ( ENTER),
+ SHAPE_INTERPOLATION__SELECT_FIRST_SLICE ( SHIFT_DOWN + LEFT),
+ SHAPE_INTERPOLATION__SELECT_LAST_SLICE ( SHIFT_DOWN + RIGHT),
+ SHAPE_INTERPOLATION__SELECT_PREVIOUS_SLICE ( LEFT),
+ SHAPE_INTERPOLATION__SELECT_NEXT_SLICE ( RIGHT ),
+ SHAPE_INTERPOLATION__REMOVE_SLICE_1 ( DELETE, "delete current slice "),
+ SHAPE_INTERPOLATION__REMOVE_SLICE_2 ( BACK_SPACE, "delete current slice "),
+ SHAPE_INTERPOLATION__AUTO_SAM__NEW_SLICE_LEFT ( OPEN_BRACKET, "shape interpolation: auto SAM: new slice left" ),
+ SHAPE_INTERPOLATION__AUTO_SAM__NEW_SLICES_BISECT ( QUOTE, "shape interpolation: auto SAM: new slice between closest slices" ),
+ SHAPE_INTERPOLATION__AUTO_SAM__NEW_SLICES_BISECT_ALL ( SHIFT_DOWN + QUOTE, "shape interpolation: auto SAM: new slice between all slices" ),
+ SHAPE_INTERPOLATION__AUTO_SAM__NEW_SLICE_RIGHT ( CLOSE_BRACKET, "shape interpolation: auto SAM: new slice right" ),
+ SHAPE_INTERPOLATION__AUTO_SAM__NEW_SLICE_HERE ( SHIFT_DOWN + A, "shape interpolation: auto SAM: new slice at current location" ),
;
diff --git a/src/main/kotlin/org/janelia/saalfeldlab/paintera/config/LoggingConfig.kt b/src/main/kotlin/org/janelia/saalfeldlab/paintera/config/LoggingConfig.kt
index a37de627b..103f0501b 100644
--- a/src/main/kotlin/org/janelia/saalfeldlab/paintera/config/LoggingConfig.kt
+++ b/src/main/kotlin/org/janelia/saalfeldlab/paintera/config/LoggingConfig.kt
@@ -28,8 +28,7 @@ class LoggingConfig {
.apply { addTriggeredListener { _, _, level -> LogUtils.rootLoggerLevel = level } }
var rootLoggerLevel: Level by rootLoggerLevelProperty.nonnull()
- val isLoggingEnabledProperty = SimpleBooleanProperty(DEFAULT_IS_LOGGING_ENABLED)
- .apply {
+ val isLoggingEnabledProperty = SimpleBooleanProperty(DEFAULT_IS_LOGGING_ENABLED).apply {
addTriggeredListener { _, _, new ->
LogUtils.setLoggingEnabled(new)
loggerLevels.forEach { (logger, level) -> LogUtils.setLogLevelFor(logger, level.get()) }
@@ -37,8 +36,7 @@ class LoggingConfig {
}
var isLoggingEnabled: Boolean by isLoggingEnabledProperty.nonnull()
- val isLoggingToConsoleEnabledProperty = SimpleBooleanProperty(DEFAULT_IS_LOGGING_TO_CONSOLE_ENABLED)
- .apply {
+ val isLoggingToConsoleEnabledProperty = SimpleBooleanProperty(DEFAULT_IS_LOGGING_TO_CONSOLE_ENABLED).apply {
addTriggeredListener { _, _, new ->
LogUtils.setLoggingToConsoleEnabled(new)
loggerLevels.forEach { (logger, level) -> LogUtils.setLogLevelFor(logger, level.get()) }
@@ -46,8 +44,7 @@ class LoggingConfig {
}
var isLoggingToConsoleEnabled: Boolean by isLoggingEnabledProperty.nonnull()
- val isLoggingToFileEnabledProperty = SimpleBooleanProperty(DEFAULT_IS_LOGGING_TO_FILE_ENABLED)
- .apply {
+ val isLoggingToFileEnabledProperty = SimpleBooleanProperty(DEFAULT_IS_LOGGING_TO_FILE_ENABLED).apply {
addTriggeredListener { _, _, new ->
LogUtils.setLoggingToFileEnabled(new)
loggerLevels.forEach { (logger, level) -> LogUtils.setLogLevelFor(logger, level.get()) }
diff --git a/src/main/kotlin/org/janelia/saalfeldlab/paintera/config/LoggingConfigNode.kt b/src/main/kotlin/org/janelia/saalfeldlab/paintera/config/LoggingConfigNode.kt
index e1b2a781f..d6f64bda1 100644
--- a/src/main/kotlin/org/janelia/saalfeldlab/paintera/config/LoggingConfigNode.kt
+++ b/src/main/kotlin/org/janelia/saalfeldlab/paintera/config/LoggingConfigNode.kt
@@ -2,6 +2,7 @@ package org.janelia.saalfeldlab.paintera.config
import ch.qos.logback.classic.Level
import ch.qos.logback.classic.Logger
+import ch.qos.logback.classic.LoggerContext
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon
import javafx.beans.InvalidationListener
import javafx.beans.property.ObjectProperty
@@ -20,10 +21,12 @@ import org.janelia.saalfeldlab.fx.Buttons
import org.janelia.saalfeldlab.fx.Labels
import org.janelia.saalfeldlab.fx.TitledPanes
import org.janelia.saalfeldlab.fx.extensions.TitledPaneExtensions
+import org.janelia.saalfeldlab.fx.ui.MatchSelection
import org.janelia.saalfeldlab.fx.ui.NamedNode
import org.janelia.saalfeldlab.paintera.ui.FontAwesome
import org.janelia.saalfeldlab.paintera.ui.PainteraAlerts
import org.janelia.saalfeldlab.paintera.util.logging.LogUtils
+import org.slf4j.LoggerFactory
class LoggingConfigNode(private val config: LoggingConfig) {
@@ -122,12 +125,25 @@ class LoggingConfigNode(private val config: LoggingConfig) {
}
}
}
+
+ val loggerList = FXCollections.observableArrayList()
+ val updateLoggerList = {
+ (LoggerFactory.getILoggerFactory() as? LoggerContext)?.let {
+ loggerList.setAll(it.loggerList.map { logger -> logger.name }.toList())
+ }
+ }
val newLoggerField = TextField("")
- val newLoggerChoiceBox = logLevelChoiceBox(null)
+ val matcherField = MatchSelection.fuzzyTop(loggerList, { name -> newLoggerField.text = name }, 5)
+ matcherField.emptyBehavior = MatchSelection.EmptyBehavior.MATCH_NONE
+ matcherField.promptText = "Search for Loggers..."
+ matcherField.focusedProperty().addListener { _, _, focused -> if (focused) updateLoggerList() }
+
+ val newLogLevelChoiceBox = logLevelChoiceBox(null)
val newLoggerButton = Buttons
- .withTooltip(null) { config.setLogLevelFor(newLoggerField.text, newLoggerChoiceBox.value) }
+ .withTooltip(null) { config.setLogLevelFor(newLoggerField.text, newLogLevelChoiceBox.value) }
.also { it.graphic = FontAwesome[FontAwesomeIcon.PLUS, 2.0] }
val listener = InvalidationListener {
+ updateLoggerList()
val name = newLoggerField.text
val isRootLoggerName = LogUtils.rootLogger.name == name
val isExistingLogger = name in keys
@@ -147,11 +163,13 @@ class LoggingConfigNode(private val config: LoggingConfig) {
newLoggerField.textProperty().addListener(listener)
listener.invalidated(newLoggerField.textProperty())
add(newLoggerField, 0, row)
- add(newLoggerChoiceBox, 1, row)
+ add(newLogLevelChoiceBox, 1, row)
add(newLoggerButton, 2, row)
+ add(matcherField, 0, row + 1, GridPane.REMAINING, GridPane.REMAINING)
+ GridPane.setHgrow(matcherField, Priority.ALWAYS)
+ GridPane.setVgrow(matcherField, Priority.ALWAYS)
+ matcherField.maxWidthProperty().bind(widthProperty())
}
-
-
}
}
diff --git a/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/ShapeInterpolationController.kt b/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/ShapeInterpolationController.kt
index a5063f513..b86a11399 100644
--- a/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/ShapeInterpolationController.kt
+++ b/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/ShapeInterpolationController.kt
@@ -56,6 +56,7 @@ import org.janelia.saalfeldlab.paintera.id.IdService
import org.janelia.saalfeldlab.paintera.stream.AbstractHighlightingARGBStream
import org.janelia.saalfeldlab.paintera.stream.HighlightingStreamConverter
import org.janelia.saalfeldlab.paintera.util.IntervalHelpers
+import org.janelia.saalfeldlab.paintera.util.IntervalHelpers.Companion.extendBy
import org.janelia.saalfeldlab.paintera.util.IntervalHelpers.Companion.smallestContainingInterval
import org.janelia.saalfeldlab.util.*
import org.slf4j.LoggerFactory
@@ -187,11 +188,9 @@ class ShapeInterpolationController>(
?.also { repaintInterval ->
if (preview) {
isBusy = true
- requestRepaintInterval = repaintInterval union requestRepaintInterval
interpolateBetweenSlices(false)
- } else {
- requestRepaintAfterTasks(repaintInterval)
}
+ requestRepaintAfterTasks(repaintInterval)
}
}
@@ -364,6 +363,7 @@ class ShapeInterpolationController>(
Last
}
+ //TODO Caleb: Controller should not move, let the tool/mode do that
fun editSelection(choice: EditSelectionChoice) {
val slices = slicesAndInterpolants.slices
when (choice) {
@@ -751,7 +751,9 @@ class ShapeInterpolationController>(
val interpolatedMaskView = interpolant.dataInterpolant
.affine(viewerMask.currentGlobalToMaskTransform)
.interval(interpolantIntervalSliceInMaskSpace)
- val fillMaskOverInterval = viewerMask.viewerImg.interval(interpolantIntervalSliceInMaskSpace)
+ val fillMaskOverInterval = viewerMask.viewerImg.apply {
+ extendValue(Label.INVALID)
+ }.interval(interpolantIntervalSliceInMaskSpace)
LoopBuilder.setImages(interpolatedMaskView, fillMaskOverInterval)
.multiThreaded()
@@ -1164,7 +1166,7 @@ class ShapeInterpolationController>(
computeBoundingBoxInInitialMask()
}
val globalBoundingBox: RealInterval?
- get() = maskBoundingBox?.let { mask.initialGlobalToMaskTransform.inverse().estimateBounds(it) }
+ get() = maskBoundingBox?.let { mask.initialMaskToGlobalWithDepthTransform.estimateBounds(it.extendBy(0.0, 0.0, .5)) }
private val selectionIntervals: MutableList = mutableListOf()
diff --git a/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/modes/Modes.kt b/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/modes/Modes.kt
index 505ccc779..5d94020cf 100644
--- a/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/modes/Modes.kt
+++ b/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/modes/Modes.kt
@@ -21,14 +21,20 @@ import org.janelia.saalfeldlab.fx.actions.ActionSet
import org.janelia.saalfeldlab.fx.actions.ActionSet.Companion.installActionSet
import org.janelia.saalfeldlab.fx.actions.ActionSet.Companion.removeActionSet
import org.janelia.saalfeldlab.fx.actions.painteraActionSet
-import org.janelia.saalfeldlab.fx.extensions.*
+import org.janelia.saalfeldlab.fx.extensions.addTriggeredListener
+import org.janelia.saalfeldlab.fx.extensions.createNullableValueBinding
+import org.janelia.saalfeldlab.fx.extensions.nullable
+import org.janelia.saalfeldlab.fx.extensions.nullableVal
import org.janelia.saalfeldlab.fx.ortho.OrthogonalViews.ViewerAndTransforms
import org.janelia.saalfeldlab.fx.ui.ActionBar
import org.janelia.saalfeldlab.fx.util.InvokeOnJavaFXApplicationThread
import org.janelia.saalfeldlab.paintera.PainteraBaseKeys
import org.janelia.saalfeldlab.paintera.config.input.KeyAndMouseBindings
import org.janelia.saalfeldlab.paintera.control.actions.AllowedActions
-import org.janelia.saalfeldlab.paintera.control.tools.*
+import org.janelia.saalfeldlab.paintera.control.tools.REQUIRES_ACTIVE_VIEWER
+import org.janelia.saalfeldlab.paintera.control.tools.Tool
+import org.janelia.saalfeldlab.paintera.control.tools.ToolBarItem
+import org.janelia.saalfeldlab.paintera.control.tools.ViewerTool
import org.janelia.saalfeldlab.paintera.control.tools.paint.PaintTool
import org.janelia.saalfeldlab.paintera.paintera
import org.janelia.saalfeldlab.paintera.state.SourceState
@@ -87,7 +93,6 @@ interface ToolMode : SourceMode {
activeTool?.deactivate()
showToolBars()
- modeToolsBar.toggleGroup?.toggles?.forEach { it.isSelected = false }
tool?.activate()
activeTool = tool
@@ -136,7 +141,6 @@ interface ToolMode : SourceMode {
/* when the active tool changes, update the toggle to reflect the active tool */
activeToolProperty.addTriggeredListener { _, old, new ->
- old?.let { toolActionsBar.reset() }
new?.let { newTool ->
toggles
.firstOrNull { it.userData == newTool }
@@ -234,7 +238,7 @@ interface ToolMode : SourceMode {
/* temporarily revoke permissions, so no actions are performed until we receive event [T] */
paintera.baseView.allowedActionsProperty().suspendPermisssions()
- val queue = LinkedBlockingQueue()
+ val queue = LinkedBlockingQueue()
InvokeOnJavaFXApplicationThread {
lateinit var escapeFilter: EventHandler
@@ -244,7 +248,7 @@ interface ToolMode : SourceMode {
removeEventFilter(event, waitForEventFilter)
removeEventFilter(KEY_PRESSED, escapeFilter)
paintera.baseView.allowedActionsProperty().restorePermisssions()
- queue.offer(it)
+ queue.offer(it ?: Any())
}
waitForEventFilter = EventHandler {
@@ -261,7 +265,7 @@ interface ToolMode : SourceMode {
addEventFilter(KEY_PRESSED, escapeFilter)
}
- return queue.take()
+ return (queue.take() as? T)
}
fun disableUnfocusedViewers() {
diff --git a/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/modes/NavigationControlMode.kt b/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/modes/NavigationControlMode.kt
index 521168f15..bf4cad640 100644
--- a/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/modes/NavigationControlMode.kt
+++ b/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/modes/NavigationControlMode.kt
@@ -259,11 +259,11 @@ object NavigationTool : ViewerTool() {
painteraMidiActionSet("midi translate along normal", device, target, NavigationActionType.Slice) {
MidiPotentiometerEvent.POTENTIOMETER_RELATIVE(2) {
name = "midi_normal"
- setDisplayType(DisplayType.TRIM)
+ displayType = DisplayType.TRIM
verifyEventNotNull()
onAction {
InvokeOnJavaFXApplicationThread {
- translator.translate(0.0, 0.0, it!!.value.sign * FAST)
+ translator.translate(0.0, 0.0, it!!.value.toInt().sign * FAST)
}
}
}
@@ -294,7 +294,7 @@ object NavigationTool : ViewerTool() {
painteraMidiActionSet("midi translate xy", device, target, NavigationActionType.Pan) {
MidiPotentiometerEvent.POTENTIOMETER_RELATIVE(0) {
name = "midi translate x"
- setDisplayType(DisplayType.TRIM)
+ displayType = DisplayType.TRIM
verifyEventNotNull()
onAction {
InvokeOnJavaFXApplicationThread {
@@ -304,7 +304,7 @@ object NavigationTool : ViewerTool() {
}
MidiPotentiometerEvent.POTENTIOMETER_RELATIVE(1) {
name = "midi translate y"
- setDisplayType(DisplayType.TRIM)
+ displayType = DisplayType.TRIM
verifyEventNotNull()
onAction {
InvokeOnJavaFXApplicationThread {
@@ -371,7 +371,7 @@ object NavigationTool : ViewerTool() {
painteraMidiActionSet("zoom", device, target, NavigationActionType.Zoom) {
MidiPotentiometerEvent.POTENTIOMETER_RELATIVE(3) {
name = "midi_zoom"
- setDisplayType(DisplayType.TRIM)
+ displayType = DisplayType.TRIM
verifyEventNotNull()
onAction {
InvokeOnJavaFXApplicationThread {
@@ -412,7 +412,7 @@ object NavigationTool : ViewerTool() {
KEY_PRESSED(keyBindings, NavigationKeys.SET_ROTATION_AXIS_Z) { onAction { keyRotationAxis.set(Axis.Z) } }
}
- val mouseRotation = painteraDragActionSet("mousde-drag-rotate", NavigationActionType.Rotate) {
+ val mouseRotation = painteraDragActionSet("mouse-drag-rotate", NavigationActionType.Rotate) {
verify { it.isPrimaryButtonDown }
dragDetectedAction.verify { NavigationTool.allowRotationsProperty() }
onDragDetected {
@@ -455,7 +455,7 @@ object NavigationTool : ViewerTool() {
DeviceManager.xTouchMini?.let { device ->
targetPositionObservable?.let { targetPos ->
painteraMidiActionSet(NavigationKeys.REMOVE_ROTATION, device, target, NavigationActionType.Rotate) {
- MidiButtonEvent.BUTTON_PRESED(18) {
+ MidiButtonEvent.BUTTON_PRESSED(18) {
name = "midi_remove_rotation"
verifyEventNotNull()
onAction { InvokeOnJavaFXApplicationThread { resetRotationController.removeRotationCenteredAt(targetPos.x, targetPos.y) } }
@@ -480,12 +480,12 @@ object NavigationTool : ViewerTool() {
painteraMidiActionSet("rotate", device, target, NavigationActionType.Rotate) {
MidiPotentiometerEvent.POTENTIOMETER_RELATIVE ( handle) {
name = "midi_rotate_${axis.name.lowercase()}"
- setDisplayType(DisplayType.TRIM)
+ displayType = DisplayType.TRIM
verifyEventNotNull()
verify { allowRotationsProperty() }
onAction {
InvokeOnJavaFXApplicationThread {
- val direction = it!!.value.sign
+ val direction = it!!.value.toInt().sign
rotationController.setSpeed(direction * speed)
rotationController.rotateAroundAxis(targetPosition.x, targetPosition.y, axis)
}
@@ -498,15 +498,20 @@ object NavigationTool : ViewerTool() {
}
}
- fun midiNavigationActions() = mutableListOf().also {
- midiPanActions()?.let { midiActions -> it.add(midiActions) }
- midiSliceActions()?.let { midiActions -> it.add(midiActions) }
- midiZoomActions()?.let { midiActions -> it.add(midiActions) }
- midiRotationActions()?.let { midiActions -> it.addAll(midiActions) }
- midiResetRotationAction()?.let { midiActions -> it.add(midiActions) }
+ fun midiNavigationActions(
+ pan: Boolean = true,
+ slice: Boolean = true,
+ zoom: Boolean = true,
+ rotation: Boolean = true,
+ resetRotation: Boolean = true
+ ) = mutableListOf().also {
+ if (pan) midiPanActions()?.let { midiActions -> it.add(midiActions) }
+ if (slice) midiSliceActions()?.let { midiActions -> it.add(midiActions) }
+ if (zoom) midiZoomActions()?.let { midiActions -> it.add(midiActions) }
+ if (rotation) midiRotationActions()?.let { midiActions -> it.addAll(midiActions) }
+ if (resetRotation) midiResetRotationAction()?.let { midiActions -> it.add(midiActions) }
}
-
private fun goToPositionAction(translateXYController: TranslationController) =
painteraActionSet("center on position", NavigationActionType.Pan) {
KEY_PRESSED(KeyCode.CONTROL, KeyCode.G) {
diff --git a/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/modes/PaintLabelMode.kt b/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/modes/PaintLabelMode.kt
index 6d82de49b..0ac9cfe5f 100644
--- a/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/modes/PaintLabelMode.kt
+++ b/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/modes/PaintLabelMode.kt
@@ -180,6 +180,15 @@ object PaintLabelMode : AbstractToolMode() {
}
}
+ override fun switchTool(tool: Tool?) {
+ super.switchTool(tool)
+ /*SAM Tool restrict the active ViewerPanel, so we don't want it changing on mouseover of the other views, for example */
+ if (activeTool is SamTool)
+ activeViewerProperty.unbind()
+ else if (!activeViewerProperty.isBound)
+ activeViewerProperty.bind(paintera.baseView.currentFocusHolder)
+ }
+
private fun getToolTriggers() = listOf(
paintBrushTool.createTriggers(this, PaintActionType.Paint),
diff --git a/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/modes/ShapeInterpolationMode.kt b/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/modes/ShapeInterpolationMode.kt
index 83abaede9..075023f3b 100644
--- a/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/modes/ShapeInterpolationMode.kt
+++ b/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/modes/ShapeInterpolationMode.kt
@@ -61,10 +61,10 @@ class ShapeInterpolationMode>(val controller: ShapeInterpolat
internal val samSliceCache = SamSliceCache()
- private val paintBrushTool = ShapeInterpolationPaintBrushTool(activeSourceStateProperty, this)
- private val fill2DTool = ShapeInterpolationFillTool(controller, activeSourceStateProperty, this)
- private val samTool = ShapeInterpolationSAMTool(controller, activeSourceStateProperty, this@ShapeInterpolationMode)
- private val shapeInterpolationTool = ShapeInterpolationTool(controller, previousMode, this@ShapeInterpolationMode, this@ShapeInterpolationMode.fill2DTool)
+ private val paintBrushTool by lazy { ShapeInterpolationPaintBrushTool(activeSourceStateProperty, this) }
+ private val fill2DTool by lazy { ShapeInterpolationFillTool(controller, activeSourceStateProperty, this) }
+ private val samTool by lazy { ShapeInterpolationSAMTool(controller, activeSourceStateProperty, this@ShapeInterpolationMode) }
+ private val shapeInterpolationTool by lazy { ShapeInterpolationTool(controller, previousMode, this@ShapeInterpolationMode, this@ShapeInterpolationMode.fill2DTool) }
override val defaultTool: Tool? by lazy { shapeInterpolationTool }
override val modeActions by lazy { modeActions() }
@@ -275,27 +275,31 @@ class ShapeInterpolationMode>(val controller: ShapeInterpolat
}
}
with(controller) {
- MidiButtonEvent.BUTTON_PRESED(9) {
+ MidiButtonEvent.BUTTON_PRESSED(9) {
name = "midi go to first slice"
verify { controllerState != Moving }
+ verify { activeTool !is SamTool }
onAction { editSelection(EditSelectionChoice.First) }
}
- MidiButtonEvent.BUTTON_PRESED(10) {
+ MidiButtonEvent.BUTTON_PRESSED(10) {
name = "midi go to previous slice"
verify { controllerState != Moving }
+ verify { activeTool !is SamTool }
onAction { editSelection(EditSelectionChoice.Previous) }
}
- MidiButtonEvent.BUTTON_PRESED(11) {
+ MidiButtonEvent.BUTTON_PRESSED(11) {
name = "midi go to next slice"
verify { controllerState != Moving }
+ verify { activeTool !is SamTool }
onAction { editSelection(EditSelectionChoice.Next) }
}
- MidiButtonEvent.BUTTON_PRESED(12) {
+ MidiButtonEvent.BUTTON_PRESSED(12) {
name = "midi go to last slice"
verify { controllerState != Moving }
+ verify { activeTool !is SamTool }
onAction { editSelection(EditSelectionChoice.Last) }
}
}
diff --git a/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/paint/ViewerMask.kt b/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/paint/ViewerMask.kt
index 598d52077..726467193 100644
--- a/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/paint/ViewerMask.kt
+++ b/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/paint/ViewerMask.kt
@@ -152,6 +152,7 @@ class ViewerMask private constructor(
*/
val xScaleChange get() = (Affine3DHelpers.extractScale(initialGlobalToMaskTransform, 0) / Affine3DHelpers.extractScale(currentGlobalToMaskTransform, 0))
+ val initialMaskToGlobalWithDepthTransform: AffineTransform3D = initialGlobalToMaskTransform.inverse().copy().concatenate(depthScaleTransform)
val initialMaskToSourceWithDepthTransform: AffineTransform3D = initialMaskToSourceTransform.copy().concatenate(depthScaleTransform)
val currentMaskToSourceWithDepthTransform: AffineTransform3D = currentMaskToSourceTransform.copy().concatenate(depthScaleTransform)
private val maskSourceInterval
diff --git a/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/tools/paint/PaintBrushTool.kt b/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/tools/paint/PaintBrushTool.kt
index cc18b95de..2e9424b5f 100644
--- a/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/tools/paint/PaintBrushTool.kt
+++ b/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/tools/paint/PaintBrushTool.kt
@@ -281,15 +281,15 @@ open class PaintBrushTool(activeSourceStateProperty: SimpleObjectProperty