Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modularize inspector #596

Merged
merged 10 commits into from
Jul 2, 2024
Merged
9 changes: 9 additions & 0 deletions src/main/kotlin/sc/iview/SciView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ import org.scijava.thread.ThreadService
import org.scijava.util.ColorRGB
import org.scijava.util.Colors
import org.scijava.util.VersionUtils
import sc.iview.commands.edit.InspectorInteractiveCommand
import sc.iview.event.NodeActivatedEvent
import sc.iview.event.NodeAddedEvent
import sc.iview.event.NodeChangedEvent
Expand Down Expand Up @@ -1891,6 +1892,14 @@ class SciView : SceneryBase, CalibratedRealInterval<CalibratedAxis> {
return ProjectDirectories.from("sc", "iview", "sciview")
}

/**
* Adds a new custom panel to the inspector, with a usage [condition] given (e.g., checking for a
* specific Node type, and the [panelClass] that represents the custom panel.
*/
fun addNodeSpecificInspectorPanel(condition: (Node) -> Boolean, panelClass: Class<out InspectorInteractiveCommand>) {
mainWindow.addNodeSpecificInspectorPanel(condition, panelClass)
}

companion object {
//bounds for the controls
const val FPSSPEED_MINBOUND_SLOW = 0.01f
Expand Down
68 changes: 68 additions & 0 deletions src/main/kotlin/sc/iview/commands/edit/AtmosphereProperties.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package sc.iview.commands.edit

import graphics.scenery.primitives.Atmosphere
import okio.withLock
import org.scijava.command.Command
import org.scijava.plugin.Parameter
import org.scijava.plugin.Plugin
import org.scijava.widget.NumberWidget

/**
* Inspector panel for adjusting the properties of an [Atmosphere] [graphics.scenery.Node].
*/
@Plugin(type = Command::class, initializer = "initValues", visible = false)
class AtmosphereProperties : InspectorInteractiveCommand() {
/** Field for [Atmosphere.latitude]. */
@Parameter(label = "Latitude", style = NumberWidget.SPINNER_STYLE+"group:Atmosphere"+",format:0.0", min = "-90", max = "90", stepSize = "1", callback = "updateNodeProperties")
private var atmosphereLatitude = 50f

/** Field negating [Atmosphere.isSunAnimated]. */
@Parameter(label = "Enable keybindings and manual control", style = "group:Atmosphere", callback = "updateNodeProperties", description = "Use key bindings for controlling the sun.\nCtrl + Arrow Keys = large increments.\nCtrl + Shift + Arrow keys = small increments.")
private var isSunManual = false

/** Field for [Atmosphere.azimuth]. */
@Parameter(label = "Sun Azimuth", style = "group:Atmosphere" + ",format:0.0", callback = "updateNodeProperties", description = "Azimuth value of the sun in degrees", min = "0", max = "360", stepSize = "1")
private var sunAzimuth = 180f

/** Field for [Atmosphere.elevation]. */
@Parameter(label = "Sun Elevation", style = "group:Atmosphere" + ",format:0.0", callback = "updateNodeProperties", description = "Elevation value of the sun in degrees", min = "-90", max = "90", stepSize = "1")
private var sunElevation = 45f

/** Field for [Atmosphere.emissionStrength]. */
@Parameter(label = "Emission Strength", style = NumberWidget.SPINNER_STYLE+"group:Atmosphere"+",format:0.00", min = "0", max="10", stepSize = "0.1", callback = "updateNodeProperties")
private var atmosphereStrength = 1f

/** Updates this command fields with the node's current properties. */
override fun updateCommandFields() {
val node = currentSceneNode as? Atmosphere ?: return

fieldsUpdating.withLock {

isSunManual = !node.isSunAnimated
atmosphereLatitude = node.latitude
atmosphereStrength = node.emissionStrength
sunAzimuth = node.azimuth
sunElevation = node.elevation
}
}

/** Updates current scene node properties to match command fields. */
override fun updateNodeProperties() {
val node = currentSceneNode as? Atmosphere ?: return

fieldsUpdating.withLock {
node.latitude = atmosphereLatitude
node.emissionStrength = atmosphereStrength
// attach/detach methods also handle the update of node.updateControls
if(isSunManual) {
sciView.sceneryInputHandler?.let { node.attachBehaviors(it) }
node.setSunPosition(sunElevation, sunAzimuth)
} else {
sciView.sceneryInputHandler?.let { node.detachBehaviors(it) }
// Update the sun position immediately
node.setSunPositionFromTime()
}
}
}

}
239 changes: 239 additions & 0 deletions src/main/kotlin/sc/iview/commands/edit/BasicProperties.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
/*-
* #%L
* Scenery-backed 3D visualization package for ImageJ.
* %%
* Copyright (C) 2016 - 2024 sciview developers.
* %%
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* #L%
*/
package sc.iview.commands.edit

import graphics.scenery.*
import graphics.scenery.attribute.material.HasMaterial
import net.imagej.lut.LUTService
import okio.withLock
import org.joml.Quaternionf
import org.joml.Vector3f
import org.scijava.command.Command
import org.scijava.plugin.Parameter
import org.scijava.plugin.Plugin
import org.scijava.ui.UIService
import org.scijava.util.ColorRGB
import org.scijava.widget.ChoiceWidget
import org.scijava.widget.NumberWidget
import sc.iview.event.NodeChangedEvent
import java.util.*

/**
* A command for interactively editing a node's properties.
*
* @author Robert Haase, Scientific Computing Facility, MPI-CBG Dresden
* @author Curtis Rueden
* @author Kyle Harrington
* @author Ulrik Guenther
*/
@Plugin(type = Command::class, initializer = "initValues", visible = false)
class BasicProperties : InspectorInteractiveCommand() {
@Parameter
private lateinit var uiSrv: UIService

@Parameter
private lateinit var lutService: LUTService

/* Basic properties */

@Parameter(required = false, style = ChoiceWidget.LIST_BOX_STYLE, callback = "refreshSceneNodeInDialog")
private val sceneNode: String? = null

@Parameter(label = "Visible", callback = "updateNodeProperties", style = "group:Basic")
private var visible = false

@Parameter(label = "Color", required = false, callback = "updateNodeProperties", style = "group:Basic")
private var colour: ColorRGB? = null

@Parameter(label = "Name", callback = "updateNodeProperties", style = "group:Basic")
private var name: String = ""

@Parameter(label = "Position X", style = NumberWidget.SPINNER_STYLE+ ",group:Basic" + ",format:0.000", stepSize = "0.1", callback = "updateNodeProperties")
private var positionX = 0f

@Parameter(label = "Position Y", style = NumberWidget.SPINNER_STYLE+ ",group:Basic" + ",format:0.000", stepSize = "0.1", callback = "updateNodeProperties")
private var positionY = 0f

@Parameter(label = "Position Z", style = NumberWidget.SPINNER_STYLE+ ",group:Basic" + ",format:0.000", stepSize = "0.1", callback = "updateNodeProperties")
private var positionZ = 0f


@Parameter(label = "[Rotation & Scaling]Scale X", style = NumberWidget.SPINNER_STYLE+"group:Rotation & Scaling" + ",format:0.000", stepSize = "0.1", callback = "updateNodeProperties")
private var scaleX = 1f

@Parameter(label = "[Rotation & Scaling]Scale Y", style = NumberWidget.SPINNER_STYLE+"group:Rotation & Scaling" + ",format:0.000", stepSize = "0.1", callback = "updateNodeProperties")
private var scaleY = 1f

@Parameter(label = "[Rotation & Scaling]Scale Z", style = NumberWidget.SPINNER_STYLE+"group:Rotation & Scaling" + ",format:0.000", stepSize = "0.1", callback = "updateNodeProperties")
private var scaleZ = 1f

@Parameter(label = "[Rotation & Scaling]Rotation Phi", style = NumberWidget.SPINNER_STYLE+"group:Rotation & Scaling" + ",format:0.000", min = PI_NEG, max = PI_POS, stepSize = "0.01", callback = "updateNodeProperties")
private var rotationPhi = 0f

@Parameter(label = "[Rotation & Scaling]Rotation Theta", style = NumberWidget.SPINNER_STYLE+"group:Rotation & Scaling" + ",format:0.000", min = PI_NEG, max = PI_POS, stepSize = "0.01", callback = "updateNodeProperties")
private var rotationTheta = 0f

@Parameter(label = "[Rotation & Scaling]Rotation Psi", style = NumberWidget.SPINNER_STYLE+"group:Rotation & Scaling" + ",format:0.000", min = PI_NEG, max = PI_POS, stepSize = "0.01", callback = "updateNodeProperties")
private var rotationPsi = 0f


var sceneNodeChoices = ArrayList<String>()

/**
* Nothing happens here, as cancelling the dialog is not possible.
*/
override fun cancel() {
// noop
}

private fun rebuildSceneObjectChoiceList() {
fieldsUpdating.withLock {
sceneNodeChoices = ArrayList()
var count = 0
// here, we want all nodes of the scene, not excluding PointLights and Cameras
for(node in sciView.getSceneNodes { _: Node? -> true }) {
sceneNodeChoices.add(makeIdentifier(node, count))
count++
}
val sceneNodeSelector = info.getMutableInput("sceneNode", String::class.java)
sceneNodeSelector.choices = sceneNodeChoices

//todo: if currentSceneNode is set, put it here as current item
sceneNodeSelector.setValue(this, sceneNodeChoices[sceneNodeChoices.size - 1])
refreshSceneNodeInDialog()
}
}

/**
* find out, which node is currently selected in the dialog.
*/
private fun refreshSceneNodeInDialog() {
val identifier = sceneNode //sceneNodeSelector.getValue(this);
currentSceneNode = null
var count = 0
for (node in sciView.getSceneNodes { _: Node? -> true }) {
if (identifier == makeIdentifier(node, count)) {
currentSceneNode = node
//System.out.println("current node found");
break
}
count++
}

// update property fields according to scene node properties
sciView.setActiveNode(currentSceneNode)
updateCommandFields()
if (sceneNodeChoices.size != sciView.getSceneNodes { _: Node? -> true }.size) {
rebuildSceneObjectChoiceList()
}
}

/** Updates command fields to match current scene node properties. */
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
override fun updateCommandFields() {
val node = currentSceneNode ?: return

fieldsUpdating.withLock {

// update colour
val colourVector = when {
node is PointLight -> node.emissionColor
node is HasMaterial -> node.material().diffuse
else -> Vector3f(0.5f)
}

colour = ColorRGB(
(colourVector[0] * 255).toInt(), //
(colourVector[1] * 255).toInt(), //
(colourVector[2] * 255).toInt()
)

// update visibility
visible = node.visible

// update position
val position = node.spatialOrNull()?.position ?: Vector3f(0.0f)
positionX = position[0]
positionY = position[1]
positionZ = position[2]

// update rotation
val eulerAngles = node.spatialOrNull()?.rotation?.getEulerAnglesXYZ(Vector3f()) ?: Vector3f(0.0f)
rotationPhi = eulerAngles.x()
rotationTheta = eulerAngles.y()
rotationPsi = eulerAngles.z()

// update scale
val scale = node.spatialOrNull()?.scale ?: Vector3f(1.0f)
scaleX = scale.x()
scaleY = scale.y()
scaleZ = scale.z()

name = node.name
}
}

/** Updates current scene node properties to match command fields. */
override fun updateNodeProperties() {
val node = currentSceneNode ?: return
fieldsUpdating.withLock {

// update visibility
node.visible = visible

// update colour
val cVector = Vector3f(
colour!!.red / 255f,
colour!!.green / 255f,
colour!!.blue / 255f
)
if(node is PointLight) {
node.emissionColor = cVector
} else {
node.ifMaterial {
diffuse = cVector
}
}

// update spatial properties
node.ifSpatial {
position = Vector3f(positionX, positionY, positionZ)
scale = Vector3f(scaleX, scaleY, scaleZ)
rotation = Quaternionf().rotateXYZ(rotationPhi, rotationTheta, rotationPsi)
}
node.name = name

events.publish(NodeChangedEvent(node))
}
}

private fun makeIdentifier(node: Node, count: Int): String {
return "" + node.name + "[" + count + "]"
}
}
56 changes: 56 additions & 0 deletions src/main/kotlin/sc/iview/commands/edit/BoundingGridProperties.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package sc.iview.commands.edit

import graphics.scenery.BoundingGrid
import okio.withLock
import org.joml.Vector3f
import org.scijava.command.Command
import org.scijava.plugin.Parameter
import org.scijava.plugin.Plugin
import org.scijava.util.ColorRGB

/**
* Inspector panel for inspecting a [BoundingGrid]'s properties.
*/
@Plugin(type = Command::class, initializer = "initValues", visible = false)
class BoundingGridProperties : InspectorInteractiveCommand() {
/** Parameter for the [BoundingGrid.gridColor] */
@Parameter(label = "Grid Color", callback = "updateNodeProperties", style = "group:Grid")
private var gridColor: ColorRGB? = null

/** Parameter for the [BoundingGrid.ticksOnly], determining whether to show a box or only ticks. */
@Parameter(label = "Ticks only", callback = "updateNodeProperties", style = "group:Grid")
private var ticksOnly = false

/** Updates this command fields with the node's current properties. */
override fun updateCommandFields() {
val node = currentSceneNode as? BoundingGrid ?: return

fieldsUpdating.withLock {
gridColor = ColorRGB(
node.gridColor.x().toInt() * 255,
node.gridColor.y().toInt() * 255,
node.gridColor.z().toInt() * 255
)
ticksOnly = node.ticksOnly > 0
}
}

/** Updates current scene node properties to match command fields. */
override fun updateNodeProperties() {
val node = currentSceneNode as? BoundingGrid ?: return
fieldsUpdating.withLock {

val ticks = if(ticksOnly) {
1
} else {
0
}
node.ticksOnly = ticks
node.gridColor = Vector3f(
gridColor!!.red / 255.0f,
gridColor!!.green / 255.0f,
gridColor!!.blue / 255.0f
)
}
}
}
Loading
Loading