Skip to content

Commit

Permalink
Merge pull request #596 from scenerygraphics/enhancement/inspector-mo…
Browse files Browse the repository at this point in the history
…dularization

Modularize inspector
  • Loading branch information
kephale authored Jul 2, 2024
2 parents 4c29759 + 3451df5 commit 5ceadb8
Show file tree
Hide file tree
Showing 23 changed files with 1,143 additions and 792 deletions.
2 changes: 1 addition & 1 deletion src/main/java/sc/iview/commands/demo/basic/TextDemo.java
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ public void run() {
board.setName("TextBoard");
board.setTransparent(0);
board.setFontColor(new Vector4f(0, 0, 0, 0));
board.setBackgroundColor(new Vector4f(100, 100, 0, 0));
board.setBackgroundColor(new Vector4f(1.0f, 1.0f, 0.0f, 1.0f));
board.spatial().setPosition(msh.spatialOrNull().getPosition().add(new Vector3f(0, 10, 0)));
board.spatial().setScale(new Vector3f(10.0f, 10.0f, 10.0f));

Expand Down
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 @@ -1911,6 +1912,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 = "updateCommandFields", 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()
}
}
}

}
233 changes: 233 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,233 @@
/*-
* #%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 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.util.ColorRGB
import org.scijava.widget.ChoiceWidget
import org.scijava.widget.NumberWidget
import sc.iview.event.NodeChangedEvent
import java.util.*

const val GROUP_NAME_BASIC = "group:Basic Properties"
const val GROUP_NAME_ROTATION = "group:Rotation & Scaling"

/**
* 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 = "updateCommandFields", visible = false)
class BasicProperties : InspectorInteractiveCommand() {
/* Basic properties */

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

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

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

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

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

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

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

@Parameter(label = "Scale X", style = NumberWidget.SPINNER_STYLE + GROUP_NAME_ROTATION + ",format:0.000", stepSize = "0.1", callback = "updateNodeProperties")
private var scaleX = 1f

@Parameter(label = "Scale Y", style = NumberWidget.SPINNER_STYLE + GROUP_NAME_ROTATION + ",format:0.000", stepSize = "0.1", callback = "updateNodeProperties")
private var scaleY = 1f

@Parameter(label = "Scale Z", style = NumberWidget.SPINNER_STYLE + GROUP_NAME_ROTATION + ",format:0.000", stepSize = "0.1", callback = "updateNodeProperties")
private var scaleZ = 1f

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

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

@Parameter(label = "Rotation Psi", style = NumberWidget.SPINNER_STYLE + GROUP_NAME_ROTATION + ",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)
}

color = 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(
color!!.red / 255f,
color!!.green / 255f,
color!!.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 = "updateCommandFields", 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

0 comments on commit 5ceadb8

Please sign in to comment.