Skip to content

Commit 5ceadb8

Browse files
authored
Merge pull request #596 from scenerygraphics/enhancement/inspector-modularization
Modularize inspector
2 parents 4c29759 + 3451df5 commit 5ceadb8

23 files changed

+1143
-792
lines changed

src/main/java/sc/iview/commands/demo/basic/TextDemo.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ public void run() {
116116
board.setName("TextBoard");
117117
board.setTransparent(0);
118118
board.setFontColor(new Vector4f(0, 0, 0, 0));
119-
board.setBackgroundColor(new Vector4f(100, 100, 0, 0));
119+
board.setBackgroundColor(new Vector4f(1.0f, 1.0f, 0.0f, 1.0f));
120120
board.spatial().setPosition(msh.spatialOrNull().getPosition().add(new Vector3f(0, 10, 0)));
121121
board.spatial().setScale(new Vector3f(10.0f, 10.0f, 10.0f));
122122

src/main/kotlin/sc/iview/SciView.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ import org.scijava.thread.ThreadService
100100
import org.scijava.util.ColorRGB
101101
import org.scijava.util.Colors
102102
import org.scijava.util.VersionUtils
103+
import sc.iview.commands.edit.InspectorInteractiveCommand
103104
import sc.iview.event.NodeActivatedEvent
104105
import sc.iview.event.NodeAddedEvent
105106
import sc.iview.event.NodeChangedEvent
@@ -1911,6 +1912,14 @@ class SciView : SceneryBase, CalibratedRealInterval<CalibratedAxis> {
19111912
return ProjectDirectories.from("sc", "iview", "sciview")
19121913
}
19131914

1915+
/**
1916+
* Adds a new custom panel to the inspector, with a usage [condition] given (e.g., checking for a
1917+
* specific Node type, and the [panelClass] that represents the custom panel.
1918+
*/
1919+
fun addNodeSpecificInspectorPanel(condition: (Node) -> Boolean, panelClass: Class<out InspectorInteractiveCommand>) {
1920+
mainWindow.addNodeSpecificInspectorPanel(condition, panelClass)
1921+
}
1922+
19141923
companion object {
19151924
//bounds for the controls
19161925
const val FPSSPEED_MINBOUND_SLOW = 0.01f
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package sc.iview.commands.edit
2+
3+
import graphics.scenery.primitives.Atmosphere
4+
import okio.withLock
5+
import org.scijava.command.Command
6+
import org.scijava.plugin.Parameter
7+
import org.scijava.plugin.Plugin
8+
import org.scijava.widget.NumberWidget
9+
10+
/**
11+
* Inspector panel for adjusting the properties of an [Atmosphere] [graphics.scenery.Node].
12+
*/
13+
@Plugin(type = Command::class, initializer = "updateCommandFields", visible = false)
14+
class AtmosphereProperties : InspectorInteractiveCommand() {
15+
/** Field for [Atmosphere.latitude]. */
16+
@Parameter(label = "Latitude", style = NumberWidget.SPINNER_STYLE+"group:Atmosphere"+",format:0.0", min = "-90", max = "90", stepSize = "1", callback = "updateNodeProperties")
17+
private var atmosphereLatitude = 50f
18+
19+
/** Field negating [Atmosphere.isSunAnimated]. */
20+
@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.")
21+
private var isSunManual = false
22+
23+
/** Field for [Atmosphere.azimuth]. */
24+
@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")
25+
private var sunAzimuth = 180f
26+
27+
/** Field for [Atmosphere.elevation]. */
28+
@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")
29+
private var sunElevation = 45f
30+
31+
/** Field for [Atmosphere.emissionStrength]. */
32+
@Parameter(label = "Emission Strength", style = NumberWidget.SPINNER_STYLE+"group:Atmosphere"+",format:0.00", min = "0", max="10", stepSize = "0.1", callback = "updateNodeProperties")
33+
private var atmosphereStrength = 1f
34+
35+
/** Updates this command fields with the node's current properties. */
36+
override fun updateCommandFields() {
37+
val node = currentSceneNode as? Atmosphere ?: return
38+
39+
fieldsUpdating.withLock {
40+
41+
isSunManual = !node.isSunAnimated
42+
atmosphereLatitude = node.latitude
43+
atmosphereStrength = node.emissionStrength
44+
sunAzimuth = node.azimuth
45+
sunElevation = node.elevation
46+
}
47+
}
48+
49+
/** Updates current scene node properties to match command fields. */
50+
override fun updateNodeProperties() {
51+
val node = currentSceneNode as? Atmosphere ?: return
52+
53+
fieldsUpdating.withLock {
54+
node.latitude = atmosphereLatitude
55+
node.emissionStrength = atmosphereStrength
56+
// attach/detach methods also handle the update of node.updateControls
57+
if(isSunManual) {
58+
sciView.sceneryInputHandler?.let { node.attachBehaviors(it) }
59+
node.setSunPosition(sunElevation, sunAzimuth)
60+
} else {
61+
sciView.sceneryInputHandler?.let { node.detachBehaviors(it) }
62+
// Update the sun position immediately
63+
node.setSunPositionFromTime()
64+
}
65+
}
66+
}
67+
68+
}
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
/*-
2+
* #%L
3+
* Scenery-backed 3D visualization package for ImageJ.
4+
* %%
5+
* Copyright (C) 2016 - 2024 sciview developers.
6+
* %%
7+
* Redistribution and use in source and binary forms, with or without
8+
* modification, are permitted provided that the following conditions are met:
9+
*
10+
* 1. Redistributions of source code must retain the above copyright notice,
11+
* this list of conditions and the following disclaimer.
12+
* 2. Redistributions in binary form must reproduce the above copyright notice,
13+
* this list of conditions and the following disclaimer in the documentation
14+
* and/or other materials provided with the distribution.
15+
*
16+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
20+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26+
* POSSIBILITY OF SUCH DAMAGE.
27+
* #L%
28+
*/
29+
package sc.iview.commands.edit
30+
31+
import graphics.scenery.*
32+
import graphics.scenery.attribute.material.HasMaterial
33+
import okio.withLock
34+
import org.joml.Quaternionf
35+
import org.joml.Vector3f
36+
import org.scijava.command.Command
37+
import org.scijava.plugin.Parameter
38+
import org.scijava.plugin.Plugin
39+
import org.scijava.util.ColorRGB
40+
import org.scijava.widget.ChoiceWidget
41+
import org.scijava.widget.NumberWidget
42+
import sc.iview.event.NodeChangedEvent
43+
import java.util.*
44+
45+
const val GROUP_NAME_BASIC = "group:Basic Properties"
46+
const val GROUP_NAME_ROTATION = "group:Rotation & Scaling"
47+
48+
/**
49+
* A command for interactively editing a node's properties.
50+
*
51+
* @author Robert Haase, Scientific Computing Facility, MPI-CBG Dresden
52+
* @author Curtis Rueden
53+
* @author Kyle Harrington
54+
* @author Ulrik Guenther
55+
*/
56+
@Plugin(type = Command::class, initializer = "updateCommandFields", visible = false)
57+
class BasicProperties : InspectorInteractiveCommand() {
58+
/* Basic properties */
59+
60+
@Parameter(required = false, style = ChoiceWidget.LIST_BOX_STYLE, callback = "refreshSceneNodeInDialog")
61+
private val sceneNode: String? = null
62+
63+
@Parameter(label = "Visible", callback = "updateNodeProperties", style = GROUP_NAME_BASIC)
64+
private var visible = false
65+
66+
@Parameter(label = "Color", required = false, callback = "updateNodeProperties", style = GROUP_NAME_BASIC)
67+
private var color: ColorRGB? = null
68+
69+
@Parameter(label = "Name", callback = "updateNodeProperties", style = GROUP_NAME_BASIC)
70+
private var name: String = ""
71+
72+
@Parameter(label = "Position X", style = NumberWidget.SPINNER_STYLE + "," + GROUP_NAME_BASIC + ",format:0.000", stepSize = "0.1", callback = "updateNodeProperties")
73+
private var positionX = 0f
74+
75+
@Parameter(label = "Position Y", style = NumberWidget.SPINNER_STYLE + "," + GROUP_NAME_BASIC + ",format:0.000", stepSize = "0.1", callback = "updateNodeProperties")
76+
private var positionY = 0f
77+
78+
@Parameter(label = "Position Z", style = NumberWidget.SPINNER_STYLE + "," + GROUP_NAME_BASIC + ",format:0.000", stepSize = "0.1", callback = "updateNodeProperties")
79+
private var positionZ = 0f
80+
81+
@Parameter(label = "Scale X", style = NumberWidget.SPINNER_STYLE + GROUP_NAME_ROTATION + ",format:0.000", stepSize = "0.1", callback = "updateNodeProperties")
82+
private var scaleX = 1f
83+
84+
@Parameter(label = "Scale Y", style = NumberWidget.SPINNER_STYLE + GROUP_NAME_ROTATION + ",format:0.000", stepSize = "0.1", callback = "updateNodeProperties")
85+
private var scaleY = 1f
86+
87+
@Parameter(label = "Scale Z", style = NumberWidget.SPINNER_STYLE + GROUP_NAME_ROTATION + ",format:0.000", stepSize = "0.1", callback = "updateNodeProperties")
88+
private var scaleZ = 1f
89+
90+
@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")
91+
private var rotationPhi = 0f
92+
93+
@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")
94+
private var rotationTheta = 0f
95+
96+
@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")
97+
private var rotationPsi = 0f
98+
99+
100+
var sceneNodeChoices = ArrayList<String>()
101+
102+
/**
103+
* Nothing happens here, as cancelling the dialog is not possible.
104+
*/
105+
override fun cancel() {
106+
// noop
107+
}
108+
109+
private fun rebuildSceneObjectChoiceList() {
110+
fieldsUpdating.withLock {
111+
sceneNodeChoices = ArrayList()
112+
var count = 0
113+
// here, we want all nodes of the scene, not excluding PointLights and Cameras
114+
for(node in sciView.getSceneNodes { _: Node? -> true }) {
115+
sceneNodeChoices.add(makeIdentifier(node, count))
116+
count++
117+
}
118+
val sceneNodeSelector = info.getMutableInput("sceneNode", String::class.java)
119+
sceneNodeSelector.choices = sceneNodeChoices
120+
121+
//todo: if currentSceneNode is set, put it here as current item
122+
sceneNodeSelector.setValue(this, sceneNodeChoices[sceneNodeChoices.size - 1])
123+
refreshSceneNodeInDialog()
124+
}
125+
}
126+
127+
/**
128+
* find out, which node is currently selected in the dialog.
129+
*/
130+
private fun refreshSceneNodeInDialog() {
131+
val identifier = sceneNode //sceneNodeSelector.getValue(this);
132+
currentSceneNode = null
133+
var count = 0
134+
for (node in sciView.getSceneNodes { _: Node? -> true }) {
135+
if (identifier == makeIdentifier(node, count)) {
136+
currentSceneNode = node
137+
//System.out.println("current node found");
138+
break
139+
}
140+
count++
141+
}
142+
143+
// update property fields according to scene node properties
144+
sciView.setActiveNode(currentSceneNode)
145+
updateCommandFields()
146+
if (sceneNodeChoices.size != sciView.getSceneNodes { _: Node? -> true }.size) {
147+
rebuildSceneObjectChoiceList()
148+
}
149+
}
150+
151+
/** Updates command fields to match current scene node properties. */
152+
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
153+
override fun updateCommandFields() {
154+
val node = currentSceneNode ?: return
155+
156+
fieldsUpdating.withLock {
157+
158+
// update colour
159+
val colourVector = when {
160+
node is PointLight -> node.emissionColor
161+
node is HasMaterial -> node.material().diffuse
162+
else -> Vector3f(0.5f)
163+
}
164+
165+
color = ColorRGB(
166+
(colourVector[0] * 255).toInt(), //
167+
(colourVector[1] * 255).toInt(), //
168+
(colourVector[2] * 255).toInt()
169+
)
170+
171+
// update visibility
172+
visible = node.visible
173+
174+
// update position
175+
val position = node.spatialOrNull()?.position ?: Vector3f(0.0f)
176+
positionX = position[0]
177+
positionY = position[1]
178+
positionZ = position[2]
179+
180+
// update rotation
181+
val eulerAngles = node.spatialOrNull()?.rotation?.getEulerAnglesXYZ(Vector3f()) ?: Vector3f(0.0f)
182+
rotationPhi = eulerAngles.x()
183+
rotationTheta = eulerAngles.y()
184+
rotationPsi = eulerAngles.z()
185+
186+
// update scale
187+
val scale = node.spatialOrNull()?.scale ?: Vector3f(1.0f)
188+
scaleX = scale.x()
189+
scaleY = scale.y()
190+
scaleZ = scale.z()
191+
192+
name = node.name
193+
}
194+
}
195+
196+
/** Updates current scene node properties to match command fields. */
197+
override fun updateNodeProperties() {
198+
val node = currentSceneNode ?: return
199+
fieldsUpdating.withLock {
200+
201+
// update visibility
202+
node.visible = visible
203+
204+
// update colour
205+
val cVector = Vector3f(
206+
color!!.red / 255f,
207+
color!!.green / 255f,
208+
color!!.blue / 255f
209+
)
210+
if(node is PointLight) {
211+
node.emissionColor = cVector
212+
} else {
213+
node.ifMaterial {
214+
diffuse = cVector
215+
}
216+
}
217+
218+
// update spatial properties
219+
node.ifSpatial {
220+
position = Vector3f(positionX, positionY, positionZ)
221+
scale = Vector3f(scaleX, scaleY, scaleZ)
222+
rotation = Quaternionf().rotateXYZ(rotationPhi, rotationTheta, rotationPsi)
223+
}
224+
node.name = name
225+
226+
events.publish(NodeChangedEvent(node))
227+
}
228+
}
229+
230+
private fun makeIdentifier(node: Node, count: Int): String {
231+
return "" + node.name + "[" + count + "]"
232+
}
233+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package sc.iview.commands.edit
2+
3+
import graphics.scenery.BoundingGrid
4+
import okio.withLock
5+
import org.joml.Vector3f
6+
import org.scijava.command.Command
7+
import org.scijava.plugin.Parameter
8+
import org.scijava.plugin.Plugin
9+
import org.scijava.util.ColorRGB
10+
11+
/**
12+
* Inspector panel for inspecting a [BoundingGrid]'s properties.
13+
*/
14+
@Plugin(type = Command::class, initializer = "updateCommandFields", visible = false)
15+
class BoundingGridProperties : InspectorInteractiveCommand() {
16+
/** Parameter for the [BoundingGrid.gridColor] */
17+
@Parameter(label = "Grid Color", callback = "updateNodeProperties", style = "group:Grid")
18+
private var gridColor: ColorRGB? = null
19+
20+
/** Parameter for the [BoundingGrid.ticksOnly], determining whether to show a box or only ticks. */
21+
@Parameter(label = "Ticks only", callback = "updateNodeProperties", style = "group:Grid")
22+
private var ticksOnly = false
23+
24+
/** Updates this command fields with the node's current properties. */
25+
override fun updateCommandFields() {
26+
val node = currentSceneNode as? BoundingGrid ?: return
27+
28+
fieldsUpdating.withLock {
29+
gridColor = ColorRGB(
30+
node.gridColor.x().toInt() * 255,
31+
node.gridColor.y().toInt() * 255,
32+
node.gridColor.z().toInt() * 255
33+
)
34+
ticksOnly = node.ticksOnly > 0
35+
}
36+
}
37+
38+
/** Updates current scene node properties to match command fields. */
39+
override fun updateNodeProperties() {
40+
val node = currentSceneNode as? BoundingGrid ?: return
41+
fieldsUpdating.withLock {
42+
43+
val ticks = if(ticksOnly) {
44+
1
45+
} else {
46+
0
47+
}
48+
node.ticksOnly = ticks
49+
node.gridColor = Vector3f(
50+
gridColor!!.red / 255.0f,
51+
gridColor!!.green / 255.0f,
52+
gridColor!!.blue / 255.0f
53+
)
54+
}
55+
}
56+
}

0 commit comments

Comments
 (0)