forked from kscience/visionforge
commit
f0f117d4fc
@ -4,13 +4,14 @@
|
|||||||
### Added
|
### Added
|
||||||
- Server module
|
- Server module
|
||||||
- Change collector
|
- Change collector
|
||||||
|
- Customizable accessors for colors
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Vision does not implement ItemProvider anymore. Property changes are done via `getProperty`/`setProperty` and `property` delegate.
|
- Vision does not implement ItemProvider anymore. Property changes are done via `getProperty`/`setProperty` and `property` delegate.
|
||||||
- Point3D and Point2D are made separate classes instead of expect/actual (to split up different engines.
|
- Point3D and Point2D are made separate classes instead of expect/actual (to split up different engines.
|
||||||
- JavaFX support moved to a separate module
|
- JavaFX support moved to a separate module
|
||||||
- Threejs support moved to a separate module
|
- Threejs support moved to a separate module
|
||||||
- \[Breaking change!\] Stylesheets are moved into properties under `@stylesheet` key
|
- \[Format breaking change!\] Stylesheets are moved into properties under `@stylesheet` key
|
||||||
|
|
||||||
### Deprecated
|
### Deprecated
|
||||||
|
|
||||||
|
@ -2,8 +2,8 @@ plugins {
|
|||||||
id("ru.mipt.npm.project")
|
id("ru.mipt.npm.project")
|
||||||
}
|
}
|
||||||
|
|
||||||
val dataforgeVersion by extra("0.2.1-dev-4")
|
val dataforgeVersion by extra("0.3.0-dev")
|
||||||
val ktorVersion by extra("1.4.3")
|
val ktorVersion by extra("1.5.0")
|
||||||
val htmlVersion by extra("0.7.2")
|
val htmlVersion by extra("0.7.2")
|
||||||
val kotlinWrappersVersion by extra("pre.129-kotlin-1.4.20")
|
val kotlinWrappersVersion by extra("pre.129-kotlin-1.4.20")
|
||||||
val fxVersion by extra("14")
|
val fxVersion by extra("14")
|
||||||
|
@ -2,6 +2,7 @@ package hep.dataforge.vision.gdml.demo
|
|||||||
|
|
||||||
import hep.dataforge.context.Global
|
import hep.dataforge.context.Global
|
||||||
import hep.dataforge.vision.VisionManager
|
import hep.dataforge.vision.VisionManager
|
||||||
|
import hep.dataforge.vision.describedProperties
|
||||||
import hep.dataforge.vision.editor.VisualObjectEditorFragment
|
import hep.dataforge.vision.editor.VisualObjectEditorFragment
|
||||||
import hep.dataforge.vision.editor.VisualObjectTreeFragment
|
import hep.dataforge.vision.editor.VisualObjectTreeFragment
|
||||||
import hep.dataforge.vision.gdml.toVision
|
import hep.dataforge.vision.gdml.toVision
|
||||||
@ -26,7 +27,7 @@ class GDMLView : View() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val propertyEditor = VisualObjectEditorFragment {
|
private val propertyEditor = VisualObjectEditorFragment {
|
||||||
it.allProperties
|
it.describedProperties
|
||||||
}.apply {
|
}.apply {
|
||||||
descriptorProperty.set(SolidMaterial.descriptor)
|
descriptorProperty.set(SolidMaterial.descriptor)
|
||||||
itemProperty.bind(treeFragment.selectedProperty)
|
itemProperty.bind(treeFragment.selectedProperty)
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package hep.dataforge.vision.gdml.demo
|
package hep.dataforge.vision.gdml.demo
|
||||||
|
|
||||||
import hep.dataforge.meta.DFExperimental
|
import hep.dataforge.meta.DFExperimental
|
||||||
import hep.dataforge.meta.setItem
|
|
||||||
import hep.dataforge.values.asValue
|
import hep.dataforge.values.asValue
|
||||||
import hep.dataforge.vision.gdml.readFile
|
import hep.dataforge.vision.gdml.readFile
|
||||||
import hep.dataforge.vision.gdml.toVision
|
import hep.dataforge.vision.gdml.toVision
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package ru.mipt.npm.muon.monitor
|
package ru.mipt.npm.muon.monitor
|
||||||
|
|
||||||
import hep.dataforge.vision.removeAll
|
import hep.dataforge.vision.removeAll
|
||||||
import hep.dataforge.vision.setProperty
|
|
||||||
import hep.dataforge.vision.solid.*
|
import hep.dataforge.vision.solid.*
|
||||||
import ru.mipt.npm.muon.monitor.Monitor.CENTRAL_LAYER_Z
|
import ru.mipt.npm.muon.monitor.Monitor.CENTRAL_LAYER_Z
|
||||||
import ru.mipt.npm.muon.monitor.Monitor.LOWER_LAYER_Z
|
import ru.mipt.npm.muon.monitor.Monitor.LOWER_LAYER_Z
|
||||||
@ -53,7 +52,6 @@ class Model {
|
|||||||
detector(it)
|
detector(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tracks = group("tracks")
|
tracks = group("tracks")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,7 +61,6 @@ class Model {
|
|||||||
|
|
||||||
fun reset() {
|
fun reset() {
|
||||||
map.values.forEach {
|
map.values.forEach {
|
||||||
it.config
|
|
||||||
it.setProperty(SolidMaterial.MATERIAL_COLOR_KEY, null)
|
it.setProperty(SolidMaterial.MATERIAL_COLOR_KEY, null)
|
||||||
}
|
}
|
||||||
tracks.removeAll()
|
tracks.removeAll()
|
||||||
|
@ -9,8 +9,8 @@ import hep.dataforge.vision.Vision
|
|||||||
import hep.dataforge.vision.bootstrap.canvasControls
|
import hep.dataforge.vision.bootstrap.canvasControls
|
||||||
import hep.dataforge.vision.bootstrap.card
|
import hep.dataforge.vision.bootstrap.card
|
||||||
import hep.dataforge.vision.bootstrap.gridRow
|
import hep.dataforge.vision.bootstrap.gridRow
|
||||||
|
import hep.dataforge.vision.bootstrap.visionPropertyEditor
|
||||||
import hep.dataforge.vision.react.ThreeCanvasComponent
|
import hep.dataforge.vision.react.ThreeCanvasComponent
|
||||||
import hep.dataforge.vision.react.configEditor
|
|
||||||
import hep.dataforge.vision.react.flexColumn
|
import hep.dataforge.vision.react.flexColumn
|
||||||
import hep.dataforge.vision.react.objectTree
|
import hep.dataforge.vision.react.objectTree
|
||||||
import hep.dataforge.vision.solid.specifications.Camera
|
import hep.dataforge.vision.solid.specifications.Camera
|
||||||
@ -57,7 +57,7 @@ val MMApp = functionalComponent<MMAppProps>("Muon monitor") { props ->
|
|||||||
|
|
||||||
val root = props.model.root
|
val root = props.model.root
|
||||||
|
|
||||||
gridRow{
|
gridRow {
|
||||||
flexColumn {
|
flexColumn {
|
||||||
css {
|
css {
|
||||||
+"col-lg-3"
|
+"col-lg-3"
|
||||||
@ -109,8 +109,8 @@ val MMApp = functionalComponent<MMAppProps>("Muon monitor") { props ->
|
|||||||
height = 100.vh
|
height = 100.vh
|
||||||
}
|
}
|
||||||
styledDiv {
|
styledDiv {
|
||||||
css{
|
css {
|
||||||
flex(0.0,1.0, FlexBasis.zero)
|
flex(0.0, 1.0, FlexBasis.zero)
|
||||||
}
|
}
|
||||||
//settings
|
//settings
|
||||||
canvas?.let {
|
canvas?.let {
|
||||||
@ -140,8 +140,8 @@ val MMApp = functionalComponent<MMAppProps>("Muon monitor") { props ->
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
styledDiv{
|
styledDiv {
|
||||||
css{
|
css {
|
||||||
padding(0.px)
|
padding(0.px)
|
||||||
}
|
}
|
||||||
nav {
|
nav {
|
||||||
@ -180,10 +180,10 @@ val MMApp = functionalComponent<MMAppProps>("Muon monitor") { props ->
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
styledDiv{
|
styledDiv {
|
||||||
css{
|
css {
|
||||||
overflowY = Overflow.auto
|
overflowY = Overflow.auto
|
||||||
}
|
}
|
||||||
//properties
|
//properties
|
||||||
card("Properties") {
|
card("Properties") {
|
||||||
selected.let { selected ->
|
selected.let { selected ->
|
||||||
@ -193,12 +193,7 @@ val MMApp = functionalComponent<MMAppProps>("Muon monitor") { props ->
|
|||||||
else -> root[selected]
|
else -> root[selected]
|
||||||
}
|
}
|
||||||
if (selectedObject != null) {
|
if (selectedObject != null) {
|
||||||
configEditor(
|
visionPropertyEditor(selectedObject, key = selected)
|
||||||
selectedObject.config,
|
|
||||||
selectedObject.descriptor,
|
|
||||||
default = selectedObject.allProperties,
|
|
||||||
key = selected
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
package ru.mipt.npm.sat
|
package ru.mipt.npm.sat
|
||||||
|
|
||||||
|
import hep.dataforge.meta.set
|
||||||
import hep.dataforge.vision.solid.*
|
import hep.dataforge.vision.solid.*
|
||||||
|
import hep.dataforge.vision.style
|
||||||
|
import hep.dataforge.vision.useStyle
|
||||||
import kotlin.math.PI
|
import kotlin.math.PI
|
||||||
|
|
||||||
internal fun visionOfSatellite(
|
internal fun visionOfSatellite(
|
||||||
@ -12,7 +15,18 @@ internal fun visionOfSatellite(
|
|||||||
ySegmentSize: Number = xSegmentSize,
|
ySegmentSize: Number = xSegmentSize,
|
||||||
fiberDiameter: Number = 1.0,
|
fiberDiameter: Number = 1.0,
|
||||||
): SolidGroup = SolidGroup {
|
): SolidGroup = SolidGroup {
|
||||||
opacity = 0.3
|
val transparent by style {
|
||||||
|
this[SolidMaterial.MATERIAL_OPACITY_KEY] = 0.3
|
||||||
|
}
|
||||||
|
|
||||||
|
val red by style {
|
||||||
|
this[SolidMaterial.MATERIAL_COLOR_KEY] = "red"
|
||||||
|
}
|
||||||
|
|
||||||
|
val blue by style {
|
||||||
|
this[SolidMaterial.MATERIAL_COLOR_KEY] = "blue"
|
||||||
|
}
|
||||||
|
|
||||||
val totalXSize = xSegments * xSegmentSize.toDouble()
|
val totalXSize = xSegments * xSegmentSize.toDouble()
|
||||||
val totalYSize = ySegments * ySegmentSize.toDouble()
|
val totalYSize = ySegments * ySegmentSize.toDouble()
|
||||||
for (layer in 1..layers) {
|
for (layer in 1..layers) {
|
||||||
@ -20,6 +34,7 @@ internal fun visionOfSatellite(
|
|||||||
for (i in 1..xSegments) {
|
for (i in 1..xSegments) {
|
||||||
for (j in 1..ySegments) {
|
for (j in 1..ySegments) {
|
||||||
box(xSegmentSize, ySegmentSize, layerHeight, name = "segment[$i,$j]") {
|
box(xSegmentSize, ySegmentSize, layerHeight, name = "segment[$i,$j]") {
|
||||||
|
useStyle(transparent)
|
||||||
z = (layer - 0.5) * layerHeight.toDouble()
|
z = (layer - 0.5) * layerHeight.toDouble()
|
||||||
x = (i - 0.5) * xSegmentSize.toDouble()
|
x = (i - 0.5) * xSegmentSize.toDouble()
|
||||||
y = (j - 0.5) * ySegmentSize.toDouble()
|
y = (j - 0.5) * ySegmentSize.toDouble()
|
||||||
@ -29,23 +44,21 @@ internal fun visionOfSatellite(
|
|||||||
group("fibers") {
|
group("fibers") {
|
||||||
for (i in 1..xSegments) {
|
for (i in 1..xSegments) {
|
||||||
cylinder(fiberDiameter, totalYSize) {
|
cylinder(fiberDiameter, totalYSize) {
|
||||||
|
useStyle(red)
|
||||||
rotationX = PI / 2
|
rotationX = PI / 2
|
||||||
z = (layer - 1.0) * layerHeight.toDouble() + fiberDiameter.toDouble()
|
z = (layer - 1.0) * layerHeight.toDouble() + fiberDiameter.toDouble()
|
||||||
x = (i - 0.5) * xSegmentSize.toDouble()
|
x = (i - 0.5) * xSegmentSize.toDouble()
|
||||||
y = totalYSize/2
|
y = totalYSize / 2
|
||||||
|
|
||||||
color("red")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (j in 1..ySegments) {
|
for (j in 1..ySegments) {
|
||||||
cylinder(fiberDiameter, totalXSize) {
|
cylinder(fiberDiameter, totalXSize) {
|
||||||
|
useStyle(blue)
|
||||||
rotationY = PI / 2
|
rotationY = PI / 2
|
||||||
z = (layer) * layerHeight.toDouble() - fiberDiameter.toDouble()
|
z = (layer) * layerHeight.toDouble() - fiberDiameter.toDouble()
|
||||||
y = (j - 0.5) * xSegmentSize.toDouble()
|
y = (j - 0.5) * xSegmentSize.toDouble()
|
||||||
x = totalXSize/2
|
x = totalXSize / 2
|
||||||
|
|
||||||
color("blue")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
package ru.mipt.npm.sat
|
package ru.mipt.npm.sat
|
||||||
|
|
||||||
|
|
||||||
|
import hep.dataforge.context.Global
|
||||||
import hep.dataforge.names.toName
|
import hep.dataforge.names.toName
|
||||||
import hep.dataforge.vision.solid.Solid
|
import hep.dataforge.vision.solid.*
|
||||||
import hep.dataforge.vision.solid.color
|
|
||||||
import hep.dataforge.vision.solid.invoke
|
|
||||||
import hep.dataforge.vision.three.server.*
|
import hep.dataforge.vision.three.server.*
|
||||||
|
import hep.dataforge.vision.visionManager
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
@ -15,10 +15,14 @@ import kotlinx.html.h1
|
|||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
|
val satContext = Global.context("sat") {
|
||||||
|
plugin(SolidManager)
|
||||||
|
}
|
||||||
|
|
||||||
//Create a geometry
|
//Create a geometry
|
||||||
val sat = visionOfSatellite(ySegments = 3)
|
val sat = visionOfSatellite(ySegments = 3)
|
||||||
|
|
||||||
val server = visionManager.serve {
|
val server = satContext.visionManager.serve {
|
||||||
//use client library
|
//use client library
|
||||||
useThreeJs()
|
useThreeJs()
|
||||||
//use css
|
//use css
|
||||||
@ -39,10 +43,11 @@ fun main() {
|
|||||||
val randomI = Random.nextInt(1, 4)
|
val randomI = Random.nextInt(1, 4)
|
||||||
val randomJ = Random.nextInt(1, 4)
|
val randomJ = Random.nextInt(1, 4)
|
||||||
val target = "layer[$randomLayer].segment[$randomI,$randomJ]".toName()
|
val target = "layer[$randomLayer].segment[$randomI,$randomJ]".toName()
|
||||||
(sat[target] as? Solid)?.color("red")
|
val targetVision = sat[target] as Solid
|
||||||
delay(300)
|
targetVision.color("red")
|
||||||
(sat[target] as? Solid)?.color("green")
|
delay(1000)
|
||||||
delay(10)
|
targetVision.color.clear()
|
||||||
|
delay(500)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,15 +39,18 @@ fun Page<Solid>.showcase() {
|
|||||||
demo("shapes", "Basic shapes") {
|
demo("shapes", "Basic shapes") {
|
||||||
box(100.0, 100.0, 100.0) {
|
box(100.0, 100.0, 100.0) {
|
||||||
z = -110.0
|
z = -110.0
|
||||||
|
color("teal")
|
||||||
}
|
}
|
||||||
sphere(50.0) {
|
sphere(50.0) {
|
||||||
x = 110
|
x = 110
|
||||||
detail = 16
|
detail = 16
|
||||||
|
color("red")
|
||||||
}
|
}
|
||||||
tube(50, height = 10, innerRadius = 25, angle = PI) {
|
tube(50, height = 10, innerRadius = 25, angle = PI) {
|
||||||
y = 110
|
y = 110
|
||||||
detail = 16
|
detail = 16
|
||||||
rotationX = PI / 4
|
rotationX = PI / 4
|
||||||
|
color("blue")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,6 +58,7 @@ fun Page<Solid>.showcase() {
|
|||||||
val group = group {
|
val group = group {
|
||||||
box(100, 100, 100) {
|
box(100, 100, 100) {
|
||||||
z = 110.0
|
z = 110.0
|
||||||
|
opacity = 0.5
|
||||||
}
|
}
|
||||||
|
|
||||||
box(100, 100, 100) {
|
box(100, 100, 100) {
|
||||||
@ -143,8 +147,7 @@ fun Page<Solid>.showcaseCSG() {
|
|||||||
detail = 32
|
detail = 32
|
||||||
}
|
}
|
||||||
material {
|
material {
|
||||||
color(Colors.red)
|
color(Colors.pink)
|
||||||
wireframe = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
composite(CompositeType.UNION) {
|
composite(CompositeType.UNION) {
|
||||||
@ -154,8 +157,8 @@ fun Page<Solid>.showcaseCSG() {
|
|||||||
sphere(50){
|
sphere(50){
|
||||||
detail = 32
|
detail = 32
|
||||||
}
|
}
|
||||||
color(Colors.lightgreen)
|
color("lightgreen")
|
||||||
opacity = 0.3
|
opacity = 0.7
|
||||||
}
|
}
|
||||||
composite(CompositeType.SUBTRACT) {
|
composite(CompositeType.SUBTRACT) {
|
||||||
y = -300
|
y = -300
|
||||||
@ -165,7 +168,7 @@ fun Page<Solid>.showcaseCSG() {
|
|||||||
sphere(50){
|
sphere(50){
|
||||||
detail = 32
|
detail = 32
|
||||||
}
|
}
|
||||||
color(Colors.teal)
|
color("teal")
|
||||||
opacity = 0.7
|
opacity = 0.7
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -173,9 +176,11 @@ fun Page<Solid>.showcaseCSG() {
|
|||||||
demo("CSG.custom", "CSG with manually created object") {
|
demo("CSG.custom", "CSG with manually created object") {
|
||||||
intersect {
|
intersect {
|
||||||
tube(60, 10) {
|
tube(60, 10) {
|
||||||
detail = 64
|
detail = 32
|
||||||
}
|
}
|
||||||
box(100, 100, 100)
|
box(100, 100, 100)
|
||||||
|
color("red")
|
||||||
|
opacity = 0.5
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -9,9 +9,7 @@ import hep.dataforge.vision.set
|
|||||||
import hep.dataforge.vision.setProperty
|
import hep.dataforge.vision.setProperty
|
||||||
import hep.dataforge.vision.solid.*
|
import hep.dataforge.vision.solid.*
|
||||||
import hep.dataforge.vision.solid.Solid.Companion.GEOMETRY_KEY
|
import hep.dataforge.vision.solid.Solid.Companion.GEOMETRY_KEY
|
||||||
import hep.dataforge.vision.solid.SolidMaterial.Companion.MATERIAL_COLOR_KEY
|
|
||||||
import hep.dataforge.vision.solid.three.*
|
import hep.dataforge.vision.solid.three.*
|
||||||
import hep.dataforge.vision.solid.three.ThreeMaterials.getMaterial
|
|
||||||
import info.laht.threekt.core.BufferGeometry
|
import info.laht.threekt.core.BufferGeometry
|
||||||
import info.laht.threekt.core.Object3D
|
import info.laht.threekt.core.Object3D
|
||||||
import info.laht.threekt.geometries.BoxBufferGeometry
|
import info.laht.threekt.geometries.BoxBufferGeometry
|
||||||
@ -31,11 +29,9 @@ internal class VariableBox(xSize: Number, ySize: Number, zSize: Number) : ThreeV
|
|||||||
scaleX = xSize
|
scaleX = xSize
|
||||||
scaleY = ySize
|
scaleY = ySize
|
||||||
scaleZ = zSize
|
scaleZ = zSize
|
||||||
config[MeshThreeFactory.EDGES_ENABLED_KEY] = false
|
|
||||||
config[MeshThreeFactory.WIREFRAME_ENABLED_KEY] = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun render(): Object3D {
|
override fun render(three: ThreePlugin): Object3D {
|
||||||
val xSize = getProperty(X_SIZE_KEY, false).number?.toDouble() ?: 1.0
|
val xSize = getProperty(X_SIZE_KEY, false).number?.toDouble() ?: 1.0
|
||||||
val ySize = getProperty(Y_SIZE_KEY, false).number?.toDouble() ?: 1.0
|
val ySize = getProperty(Y_SIZE_KEY, false).number?.toDouble() ?: 1.0
|
||||||
val zSize = getProperty(Z_SIZE_KEY, false).number?.toDouble() ?: 1.0
|
val zSize = getProperty(Z_SIZE_KEY, false).number?.toDouble() ?: 1.0
|
||||||
@ -44,9 +40,10 @@ internal class VariableBox(xSize: Number, ySize: Number, zSize: Number) : ThreeV
|
|||||||
//JS sometimes tries to pass Geometry as BufferGeometry
|
//JS sometimes tries to pass Geometry as BufferGeometry
|
||||||
@Suppress("USELESS_IS_CHECK") if (geometry !is BufferGeometry) error("BufferGeometry expected")
|
@Suppress("USELESS_IS_CHECK") if (geometry !is BufferGeometry) error("BufferGeometry expected")
|
||||||
|
|
||||||
val mesh = Mesh(geometry, getMaterial(this@VariableBox, true)).apply {
|
val mesh = Mesh(geometry, ThreeMaterials.DEFAULT).apply {
|
||||||
|
updateMaterial(this@VariableBox)
|
||||||
applyEdges(this@VariableBox)
|
applyEdges(this@VariableBox)
|
||||||
applyWireFrame(this@VariableBox)
|
//applyWireFrame(this@VariableBox)
|
||||||
|
|
||||||
//set position for mesh
|
//set position for mesh
|
||||||
updatePosition(this@VariableBox)
|
updatePosition(this@VariableBox)
|
||||||
@ -60,7 +57,7 @@ internal class VariableBox(xSize: Number, ySize: Number, zSize: Number) : ThreeV
|
|||||||
mesh.scale.set(xSize, ySize, zSize)
|
mesh.scale.set(xSize, ySize, zSize)
|
||||||
|
|
||||||
//add listener to object properties
|
//add listener to object properties
|
||||||
onPropertyChange(mesh) { name ->
|
onPropertyChange(three.context) { name ->
|
||||||
when {
|
when {
|
||||||
name.startsWith(GEOMETRY_KEY) -> {
|
name.startsWith(GEOMETRY_KEY) -> {
|
||||||
val newXSize = getProperty(X_SIZE_KEY, false).number?.toDouble() ?: 1.0
|
val newXSize = getProperty(X_SIZE_KEY, false).number?.toDouble() ?: 1.0
|
||||||
@ -69,14 +66,12 @@ internal class VariableBox(xSize: Number, ySize: Number, zSize: Number) : ThreeV
|
|||||||
mesh.scale.set(newXSize, newYSize, newZSize)
|
mesh.scale.set(newXSize, newYSize, newZSize)
|
||||||
mesh.updateMatrix()
|
mesh.updateMatrix()
|
||||||
}
|
}
|
||||||
name.startsWith(MeshThreeFactory.WIREFRAME_KEY) -> mesh.applyWireFrame(this@VariableBox)
|
|
||||||
name.startsWith(MeshThreeFactory.EDGES_KEY) -> mesh.applyEdges(this@VariableBox)
|
name.startsWith(MeshThreeFactory.EDGES_KEY) -> mesh.applyEdges(this@VariableBox)
|
||||||
name.startsWith(MATERIAL_COLOR_KEY)->{
|
//name.startsWith(MATERIAL_COLOR_KEY) -> mesh.updateMaterialProperty(this, name)
|
||||||
mesh.material = getMaterial(this, true)
|
|
||||||
}
|
|
||||||
else -> mesh.updateProperty(this@VariableBox, name)
|
else -> mesh.updateProperty(this@VariableBox, name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return mesh
|
return mesh
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,7 +95,7 @@ internal class VariableBox(xSize: Number, ySize: Number, zSize: Number) : ThreeV
|
|||||||
color(r.toUByte(), g.toUByte(), b.toUByte())
|
color(r.toUByte(), g.toUByte(), b.toUByte())
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object{
|
companion object {
|
||||||
private val X_SIZE_KEY = GEOMETRY_KEY + "xSize"
|
private val X_SIZE_KEY = GEOMETRY_KEY + "xSize"
|
||||||
private val Y_SIZE_KEY = GEOMETRY_KEY + "ySize"
|
private val Y_SIZE_KEY = GEOMETRY_KEY + "ySize"
|
||||||
private val Z_SIZE_KEY = GEOMETRY_KEY + "zSize"
|
private val Z_SIZE_KEY = GEOMETRY_KEY + "zSize"
|
||||||
|
@ -12,6 +12,16 @@ repositories{
|
|||||||
}
|
}
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
|
|
||||||
|
js(IR) {
|
||||||
|
browser {
|
||||||
|
webpackTask {
|
||||||
|
this.outputFileName = "js/visionforge-playground.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
binaries.executable()
|
||||||
|
}
|
||||||
|
|
||||||
jvm{
|
jvm{
|
||||||
compilations.all {
|
compilations.all {
|
||||||
kotlinOptions.jvmTarget = "11"
|
kotlinOptions.jvmTarget = "11"
|
||||||
@ -20,30 +30,39 @@ kotlin {
|
|||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
js(IR) {
|
|
||||||
browser {
|
afterEvaluate {
|
||||||
|
val jsBrowserDistribution by tasks.getting
|
||||||
|
|
||||||
|
tasks.getByName<ProcessResources>("jvmProcessResources") {
|
||||||
|
dependsOn(jsBrowserDistribution)
|
||||||
|
afterEvaluate {
|
||||||
|
from(jsBrowserDistribution)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
binaries.executable()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
commonMain {
|
val commonMain by getting {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":visionforge-solid"))
|
implementation(project(":visionforge-solid"))
|
||||||
implementation(project(":visionforge-gdml"))
|
implementation(project(":visionforge-gdml"))
|
||||||
|
implementation(project(":visionforge-plotly"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val jsMain by getting{
|
val jsMain by getting{
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":ui:bootstrap"))
|
implementation(project(":ui:bootstrap"))
|
||||||
|
implementation(project(":visionforge-threejs"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val jvmMain by getting{
|
val jvmMain by getting{
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation(project(":visionforge-server"))
|
||||||
implementation("com.github.Ricky12Awesome:json-schema-serialization:0.6.6")
|
implementation("com.github.Ricky12Awesome:json-schema-serialization:0.6.6")
|
||||||
implementation(project(":visionforge-threejs:visionforge-threejs-server"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
2
playground/src/commonMain/kotlin/visionContext.kt
Normal file
2
playground/src/commonMain/kotlin/visionContext.kt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
|
@ -1,58 +1,67 @@
|
|||||||
import hep.dataforge.Application
|
import hep.dataforge.context.Context
|
||||||
import hep.dataforge.startApplication
|
import hep.dataforge.context.Global
|
||||||
import hep.dataforge.vision.bootstrap.visionPropertyEditor
|
import hep.dataforge.meta.DFExperimental
|
||||||
import hep.dataforge.vision.react.ThreeCanvasComponent
|
import hep.dataforge.vision.client.VisionClient
|
||||||
import hep.dataforge.vision.react.objectTree
|
import hep.dataforge.vision.client.renderAllVisions
|
||||||
import hep.dataforge.vision.solid.*
|
import hep.dataforge.vision.plotly.PlotlyPlugin
|
||||||
import hep.dataforge.vision.solid.specifications.Canvas3DOptions
|
import hep.dataforge.vision.solid.three.ThreePlugin
|
||||||
import kotlinx.browser.document
|
import kotlinx.browser.window
|
||||||
import org.w3c.dom.HTMLElement
|
|
||||||
import react.RBuilder
|
|
||||||
import react.child
|
|
||||||
import react.dom.div
|
|
||||||
import react.dom.render
|
|
||||||
|
|
||||||
fun RBuilder.threeCanvas(object3D: Solid, options: Canvas3DOptions.() -> Unit = {}) {
|
|
||||||
child(ThreeCanvasComponent) {
|
|
||||||
attrs {
|
|
||||||
this.obj = object3D
|
|
||||||
this.options = Canvas3DOptions(options)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class PlayGroundApp : Application {
|
|
||||||
|
|
||||||
override fun start(state: Map<String, Any>) {
|
|
||||||
|
|
||||||
val element =
|
|
||||||
document.getElementById("app") as? HTMLElement ?: error("Element with id 'canvas' not found on page")
|
|
||||||
|
|
||||||
val obj = SolidGroup().apply {
|
|
||||||
box(100, 100, 100, name = "A")
|
|
||||||
group("B") {
|
|
||||||
position = Point3D(120, 0, 0)
|
|
||||||
box(100, 100, 100, name = "C")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render(element) {
|
|
||||||
div("row") {
|
|
||||||
div("col-3") {
|
|
||||||
objectTree(obj)
|
|
||||||
}
|
|
||||||
div("col-6") {
|
|
||||||
threeCanvas(obj)
|
|
||||||
}
|
|
||||||
div("col-3") {
|
|
||||||
visionPropertyEditor(obj)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
//fun RBuilder.threeCanvas(object3D: Solid, options: Canvas3DOptions.() -> Unit = {}) {
|
||||||
|
// child(ThreeCanvasComponent) {
|
||||||
|
// attrs {
|
||||||
|
// this.obj = object3D
|
||||||
|
// this.options = Canvas3DOptions(options)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//private class PlayGroundApp : Application {
|
||||||
|
//
|
||||||
|
// override fun start(state: Map<String, Any>) {
|
||||||
|
//
|
||||||
|
// val element =
|
||||||
|
// document.getElementById("app") as? HTMLElement ?: error("Element with id 'canvas' not found on page")
|
||||||
|
//
|
||||||
|
// val obj = SolidGroup().apply {
|
||||||
|
// box(100, 100, 100, name = "A")
|
||||||
|
// group("B") {
|
||||||
|
// position = Point3D(120, 0, 0)
|
||||||
|
// box(100, 100, 100, name = "C")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// render(element) {
|
||||||
|
// div("row") {
|
||||||
|
// div("col-3") {
|
||||||
|
// objectTree(obj)
|
||||||
|
// }
|
||||||
|
// div("col-6") {
|
||||||
|
// threeCanvas(obj)
|
||||||
|
// }
|
||||||
|
// div("col-3") {
|
||||||
|
// visionPropertyEditor(obj)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//}
|
||||||
|
|
||||||
|
public val visionContext: Context = Global.context("VISION") {
|
||||||
|
plugin(ThreePlugin)
|
||||||
|
plugin(PlotlyPlugin)
|
||||||
|
plugin(VisionClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@DFExperimental
|
||||||
fun main() {
|
fun main() {
|
||||||
startApplication(::PlayGroundApp)
|
//Loading three-js renderer
|
||||||
|
val clientManager = visionContext.plugins.fetch(VisionClient)
|
||||||
|
|
||||||
|
//Fetch from server and render visions for all outputs
|
||||||
|
window.onload = {
|
||||||
|
clientManager.renderAllVisions()
|
||||||
|
}
|
||||||
|
//startApplication(::PlayGroundApp)
|
||||||
}
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
package hep.dataforge.vision.solid
|
||||||
|
|
||||||
|
import hep.dataforge.meta.DFExperimental
|
||||||
|
import hep.dataforge.vision.ResourceLocation
|
||||||
|
import hep.dataforge.vision.VisionManager
|
||||||
|
import hep.dataforge.vision.html.fragment
|
||||||
|
import kotlinx.html.h1
|
||||||
|
import java.nio.file.Paths
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
@OptIn(DFExperimental::class)
|
||||||
|
fun main() {
|
||||||
|
|
||||||
|
val random = Random(112233)
|
||||||
|
val fragment = VisionManager.fragment {
|
||||||
|
h1 { +"Happy new year!" }
|
||||||
|
vision {
|
||||||
|
solid {
|
||||||
|
repeat(100) {
|
||||||
|
sphere(5, name = "sphere[$it]") {
|
||||||
|
x = random.nextDouble(-300.0, 300.0)
|
||||||
|
y = random.nextDouble(-300.0, 300.0)
|
||||||
|
z = random.nextDouble(-300.0, 300.0)
|
||||||
|
material {
|
||||||
|
color(random.nextInt())
|
||||||
|
specularColor(random.nextInt())
|
||||||
|
}
|
||||||
|
detail = 16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
visionContext.makeVisionFile(
|
||||||
|
fragment,
|
||||||
|
Paths.get("randomSpheres.html"),
|
||||||
|
resourceLocation = ResourceLocation.EMBED
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package hep.dataforge.vision.solid
|
||||||
|
|
||||||
|
import hep.dataforge.context.Context
|
||||||
|
import hep.dataforge.context.Global
|
||||||
|
import hep.dataforge.meta.DFExperimental
|
||||||
|
import hep.dataforge.vision.ResourceLocation
|
||||||
|
import hep.dataforge.vision.VisionManager
|
||||||
|
import hep.dataforge.vision.html.HtmlVisionFragment
|
||||||
|
import hep.dataforge.vision.makeVisionFile
|
||||||
|
import hep.dataforge.vision.scriptHeader
|
||||||
|
import hep.dataforge.vision.three.server.VisionServer
|
||||||
|
import hep.dataforge.vision.three.server.useScript
|
||||||
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A global vision context used to resolve different vision renderers
|
||||||
|
*/
|
||||||
|
@DFExperimental
|
||||||
|
public val visionContext: Context = Global.context("VISION") {
|
||||||
|
plugin(VisionManager)
|
||||||
|
plugin(SolidManager)
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun VisionServer.usePlayground(): Unit {
|
||||||
|
useScript("js/visionforge-playground.js")
|
||||||
|
}
|
||||||
|
|
||||||
|
@DFExperimental
|
||||||
|
public fun Context.makeVisionFile(
|
||||||
|
fragment: HtmlVisionFragment,
|
||||||
|
path: Path? = null,
|
||||||
|
title: String = "VisionForge page",
|
||||||
|
resourceLocation: ResourceLocation = ResourceLocation.SYSTEM,
|
||||||
|
show: Boolean = true,
|
||||||
|
): Unit = makeVisionFile(fragment, path = path, title = title, show = show) { actualPath ->
|
||||||
|
scriptHeader("js/visionforge-playground.js", actualPath, resourceLocation)
|
||||||
|
}
|
@ -4,8 +4,6 @@ import hep.dataforge.meta.DFExperimental
|
|||||||
import hep.dataforge.vision.ResourceLocation
|
import hep.dataforge.vision.ResourceLocation
|
||||||
import hep.dataforge.vision.VisionManager
|
import hep.dataforge.vision.VisionManager
|
||||||
import hep.dataforge.vision.html.fragment
|
import hep.dataforge.vision.html.fragment
|
||||||
import hep.dataforge.vision.three.server.makeFile
|
|
||||||
import hep.dataforge.vision.three.server.solid
|
|
||||||
|
|
||||||
@OptIn(DFExperimental::class)
|
@OptIn(DFExperimental::class)
|
||||||
fun main() {
|
fun main() {
|
||||||
@ -17,5 +15,5 @@ fun main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fragment.makeFile(resourceLocation = ResourceLocation.LOCAL)
|
visionContext.makeVisionFile(fragment = fragment, resourceLocation = ResourceLocation.SYSTEM)
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
pluginManagement {
|
pluginManagement {
|
||||||
val kotlinVersion = "1.4.20"
|
val kotlinVersion = "1.4.21"
|
||||||
val toolsVersion = "0.7.0"
|
val toolsVersion = "0.7.1"
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
@ -33,7 +33,7 @@ rootProject.name = "visionforge"
|
|||||||
include(
|
include(
|
||||||
// ":ui",
|
// ":ui",
|
||||||
":ui:react",
|
":ui:react",
|
||||||
":ui:ring",
|
// ":ui:ring",
|
||||||
// ":ui:material",
|
// ":ui:material",
|
||||||
":ui:bootstrap",
|
":ui:bootstrap",
|
||||||
":visionforge-core",
|
":visionforge-core",
|
||||||
@ -43,6 +43,7 @@ include(
|
|||||||
":visionforge-threejs:visionforge-threejs-server",
|
":visionforge-threejs:visionforge-threejs-server",
|
||||||
":visionforge-gdml",
|
":visionforge-gdml",
|
||||||
":visionforge-server",
|
":visionforge-server",
|
||||||
|
":visionforge-plotly",
|
||||||
":demo:spatial-showcase",
|
":demo:spatial-showcase",
|
||||||
":demo:gdml",
|
":demo:gdml",
|
||||||
":demo:muon-monitor",
|
":demo:muon-monitor",
|
||||||
|
@ -51,11 +51,7 @@ public val ThreeControls: FunctionalComponent<ThreeControlsProps> = functionalCo
|
|||||||
else -> (vision as? VisionGroup)?.get(selected)
|
else -> (vision as? VisionGroup)?.get(selected)
|
||||||
}
|
}
|
||||||
if (selectedObject != null) {
|
if (selectedObject != null) {
|
||||||
visionPropertyEditor(
|
visionPropertyEditor(selectedObject, key = selected)
|
||||||
selectedObject,
|
|
||||||
default = selectedObject.allProperties,
|
|
||||||
key = selected
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -69,7 +65,7 @@ public fun RBuilder.threeControls(
|
|||||||
canvas: ThreeCanvas,
|
canvas: ThreeCanvas,
|
||||||
selected: Name?,
|
selected: Name?,
|
||||||
onSelect: (Name) -> Unit = {},
|
onSelect: (Name) -> Unit = {},
|
||||||
builder: TabBuilder.() -> Unit = {}
|
builder: TabBuilder.() -> Unit = {},
|
||||||
): ReactElement = child(ThreeControls) {
|
): ReactElement = child(ThreeControls) {
|
||||||
attrs {
|
attrs {
|
||||||
this.canvas = canvas
|
this.canvas = canvas
|
||||||
|
@ -1,30 +1,38 @@
|
|||||||
package hep.dataforge.vision.bootstrap
|
package hep.dataforge.vision.bootstrap
|
||||||
|
|
||||||
import hep.dataforge.meta.Meta
|
|
||||||
import hep.dataforge.meta.descriptors.NodeDescriptor
|
import hep.dataforge.meta.descriptors.NodeDescriptor
|
||||||
import hep.dataforge.vision.Vision
|
import hep.dataforge.vision.*
|
||||||
import hep.dataforge.vision.getStyle
|
|
||||||
import hep.dataforge.vision.react.configEditor
|
|
||||||
import hep.dataforge.vision.react.metaViewer
|
import hep.dataforge.vision.react.metaViewer
|
||||||
|
import hep.dataforge.vision.react.propertyEditor
|
||||||
|
import hep.dataforge.vision.solid.SolidReference
|
||||||
import org.w3c.dom.Element
|
import org.w3c.dom.Element
|
||||||
import react.RBuilder
|
import react.RBuilder
|
||||||
import react.dom.render
|
import react.dom.render
|
||||||
|
|
||||||
public fun RBuilder.visionPropertyEditor(
|
public fun RBuilder.visionPropertyEditor(
|
||||||
item: Vision,
|
vision: Vision,
|
||||||
descriptor: NodeDescriptor? = item.descriptor,
|
descriptor: NodeDescriptor? = vision.descriptor,
|
||||||
default: Meta? = null,
|
key: Any? = null,
|
||||||
key: Any? = null
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
card("Properties") {
|
card("Properties") {
|
||||||
configEditor(item.config, descriptor, default, key)
|
propertyEditor(
|
||||||
|
provider = vision.ownProperties,
|
||||||
|
defaultProvider = vision.allProperties(),
|
||||||
|
updateFlow = vision.propertyChanges,
|
||||||
|
descriptor = descriptor,
|
||||||
|
key = key)
|
||||||
}
|
}
|
||||||
val styles = item.styles
|
val styles = if (vision is SolidReference) {
|
||||||
if(styles.isNotEmpty()) {
|
(vision.styles + vision.prototype.styles).distinct()
|
||||||
|
} else {
|
||||||
|
vision.styles
|
||||||
|
}
|
||||||
|
if (styles.isNotEmpty()) {
|
||||||
card("Styles") {
|
card("Styles") {
|
||||||
accordion("styles") {
|
accordion("styles") {
|
||||||
styles.forEach { styleName ->
|
styles.forEach { styleName ->
|
||||||
val style = item.getStyle(styleName)
|
val style = vision.getStyle(styleName)
|
||||||
if (style != null) {
|
if (style != null) {
|
||||||
entry(styleName) {
|
entry(styleName) {
|
||||||
metaViewer(style)
|
metaViewer(style)
|
||||||
@ -39,7 +47,6 @@ public fun RBuilder.visionPropertyEditor(
|
|||||||
public fun Element.visionPropertyEditor(
|
public fun Element.visionPropertyEditor(
|
||||||
item: Vision,
|
item: Vision,
|
||||||
descriptor: NodeDescriptor? = item.descriptor,
|
descriptor: NodeDescriptor? = item.descriptor,
|
||||||
default: Meta? = null
|
|
||||||
): Unit = render(this) {
|
): Unit = render(this) {
|
||||||
visionPropertyEditor(item, descriptor, default)
|
visionPropertyEditor(item, descriptor = descriptor)
|
||||||
}
|
}
|
@ -1,7 +1,8 @@
|
|||||||
package hep.dataforge.vision.react
|
package hep.dataforge.vision.react
|
||||||
|
|
||||||
import hep.dataforge.meta.Meta
|
import hep.dataforge.meta.Meta
|
||||||
import hep.dataforge.meta.MetaItem
|
import hep.dataforge.meta.NodeItem
|
||||||
|
import hep.dataforge.meta.ValueItem
|
||||||
import hep.dataforge.meta.descriptors.ItemDescriptor
|
import hep.dataforge.meta.descriptors.ItemDescriptor
|
||||||
import hep.dataforge.meta.descriptors.NodeDescriptor
|
import hep.dataforge.meta.descriptors.NodeDescriptor
|
||||||
import hep.dataforge.meta.descriptors.defaultItem
|
import hep.dataforge.meta.descriptors.defaultItem
|
||||||
@ -51,7 +52,7 @@ private fun RBuilder.metaViewerItem(props: MetaViewerProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
when (actualItem) {
|
when (actualItem) {
|
||||||
is MetaItem.NodeItem -> {
|
is NodeItem -> {
|
||||||
styledDiv {
|
styledDiv {
|
||||||
css {
|
css {
|
||||||
+TreeStyles.treeLeaf
|
+TreeStyles.treeLeaf
|
||||||
@ -108,7 +109,7 @@ private fun RBuilder.metaViewerItem(props: MetaViewerProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is MetaItem.ValueItem -> {
|
is ValueItem -> {
|
||||||
styledDiv {
|
styledDiv {
|
||||||
css {
|
css {
|
||||||
+TreeStyles.treeLeaf
|
+TreeStyles.treeLeaf
|
||||||
|
@ -7,6 +7,16 @@ import hep.dataforge.names.NameToken
|
|||||||
import hep.dataforge.names.lastOrNull
|
import hep.dataforge.names.lastOrNull
|
||||||
import hep.dataforge.names.plus
|
import hep.dataforge.names.plus
|
||||||
import hep.dataforge.values.Value
|
import hep.dataforge.values.Value
|
||||||
|
import hep.dataforge.vision.hidden
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.callbackFlow
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.html.js.onClickFunction
|
import kotlinx.html.js.onClickFunction
|
||||||
import org.w3c.dom.Element
|
import org.w3c.dom.Element
|
||||||
import org.w3c.dom.events.Event
|
import org.w3c.dom.events.Event
|
||||||
@ -14,55 +24,75 @@ import react.*
|
|||||||
import react.dom.render
|
import react.dom.render
|
||||||
import styled.*
|
import styled.*
|
||||||
|
|
||||||
public external interface ConfigEditorItemProps : RProps {
|
public external interface PropertyEditorProps : RProps {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Root config object - always non null
|
* Root config object - always non null
|
||||||
*/
|
*/
|
||||||
public var root: Config
|
public var provider: MutableItemProvider
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Full path to the displayed node in [root]. Could be empty
|
* Provide default item (greyed out if used)
|
||||||
*/
|
*/
|
||||||
public var name: Name
|
public var defaultProvider: ItemProvider?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Root default
|
* Full path to the displayed node in [provider]. Could be empty
|
||||||
*/
|
*/
|
||||||
public var default: Meta?
|
public var name: Name?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Root descriptor
|
* Root descriptor
|
||||||
*/
|
*/
|
||||||
public var descriptor: NodeDescriptor?
|
public var descriptor: NodeDescriptor?
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A coroutine scope for updates
|
||||||
|
*/
|
||||||
|
public var scope: CoroutineScope?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flow names of updated properties
|
||||||
|
*/
|
||||||
|
public var updateFlow: Flow<Name>?
|
||||||
}
|
}
|
||||||
|
|
||||||
private val ConfigEditorItem: FunctionalComponent<ConfigEditorItemProps> =
|
private val PropertyEditorItem: FunctionalComponent<PropertyEditorProps> =
|
||||||
functionalComponent("ConfigEditorItem") { props ->
|
functionalComponent("ConfigEditorItem") { props ->
|
||||||
configEditorItem(props)
|
propertyEditorItem(props)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun RBuilder.configEditorItem(props: ConfigEditorItemProps) {
|
private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
|
||||||
var expanded: Boolean by useState { true }
|
var expanded: Boolean by useState { true }
|
||||||
var item: MetaItem<Config>? by useState { props.root[props.name] }
|
val itemName = props.name ?: Name.EMPTY
|
||||||
val descriptorItem: ItemDescriptor? = props.descriptor?.get(props.name)
|
val descriptorItem: ItemDescriptor? =
|
||||||
val defaultItem = props.default?.get(props.name)
|
useMemo({ props.descriptor?.get(itemName) }, arrayOf(props.descriptor, itemName))
|
||||||
var actualItem: MetaItem<Meta>? by useState { item ?: defaultItem ?: descriptorItem?.defaultItem() }
|
|
||||||
|
|
||||||
val token = props.name.lastOrNull()?.toString() ?: "Properties"
|
var item: MetaItem? by useState { props.provider.getItem(itemName) }
|
||||||
|
|
||||||
|
if (descriptorItem?.hidden == true) return //fail fast for hidden property
|
||||||
|
|
||||||
|
var actualItem: MetaItem? by useState {
|
||||||
|
item ?: props.defaultProvider?.getItem(itemName) ?: descriptorItem?.defaultItem()
|
||||||
|
}
|
||||||
|
|
||||||
|
val token = itemName.lastOrNull()?.toString() ?: "Properties"
|
||||||
|
|
||||||
fun update() {
|
fun update() {
|
||||||
item = props.root[props.name]
|
item = props.provider.getItem(itemName)
|
||||||
actualItem = item ?: defaultItem ?: descriptorItem?.defaultItem()
|
actualItem = item ?: props.defaultProvider?.getItem(itemName) ?: descriptorItem?.defaultItem()
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffectWithCleanup(listOf(props.root)) {
|
if (props.updateFlow != null) {
|
||||||
props.root.onChange(this) { name, _, _ ->
|
useEffectWithCleanup(listOf(props.provider, props.updateFlow)) {
|
||||||
if (name == props.name) {
|
val updateJob = props.updateFlow!!.onEach { updatedName ->
|
||||||
update()
|
if (updatedName == props.name) {
|
||||||
}
|
update()
|
||||||
|
}
|
||||||
|
}.launchIn(props.scope ?: GlobalScope)
|
||||||
|
return@useEffectWithCleanup { updateJob.cancel() }
|
||||||
}
|
}
|
||||||
return@useEffectWithCleanup { props.root.removeListener(this) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val expanderClick: (Event) -> Unit = {
|
val expanderClick: (Event) -> Unit = {
|
||||||
@ -71,21 +101,19 @@ private fun RBuilder.configEditorItem(props: ConfigEditorItemProps) {
|
|||||||
|
|
||||||
val valueChanged: (Value?) -> Unit = {
|
val valueChanged: (Value?) -> Unit = {
|
||||||
if (it == null) {
|
if (it == null) {
|
||||||
props.root.remove(props.name)
|
props.provider.remove(itemName)
|
||||||
} else {
|
} else {
|
||||||
props.root[props.name] = it
|
props.provider[itemName] = it
|
||||||
}
|
}
|
||||||
update()
|
update()
|
||||||
}
|
}
|
||||||
|
|
||||||
val removeClick: (Event) -> Unit = {
|
val removeClick: (Event) -> Unit = {
|
||||||
props.root.remove(props.name)
|
props.provider.remove(itemName)
|
||||||
update()
|
update()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (actualItem is NodeItem) {
|
||||||
|
|
||||||
if (actualItem is MetaItem.NodeItem) {
|
|
||||||
styledDiv {
|
styledDiv {
|
||||||
css {
|
css {
|
||||||
+TreeStyles.treeLeaf
|
+TreeStyles.treeLeaf
|
||||||
@ -121,7 +149,6 @@ private fun RBuilder.configEditorItem(props: ConfigEditorItemProps) {
|
|||||||
add(NameToken(it))
|
add(NameToken(it))
|
||||||
}
|
}
|
||||||
item?.node?.items?.keys?.let { addAll(it) }
|
item?.node?.items?.keys?.let { addAll(it) }
|
||||||
defaultItem?.node?.items?.keys?.let { addAll(it) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
keys.filter { !it.body.startsWith("@") }.forEach { token ->
|
keys.filter { !it.body.startsWith("@") }.forEach { token ->
|
||||||
@ -129,12 +156,11 @@ private fun RBuilder.configEditorItem(props: ConfigEditorItemProps) {
|
|||||||
css {
|
css {
|
||||||
+TreeStyles.treeItem
|
+TreeStyles.treeItem
|
||||||
}
|
}
|
||||||
child(ConfigEditorItem) {
|
child(PropertyEditorItem) {
|
||||||
attrs {
|
attrs {
|
||||||
this.key = props.name.toString()
|
this.key = props.name.toString()
|
||||||
this.root = props.root
|
this.provider = props.provider
|
||||||
this.name = props.name + token
|
this.name = itemName + token
|
||||||
this.default = props.default
|
|
||||||
this.descriptor = props.descriptor
|
this.descriptor = props.descriptor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -166,7 +192,7 @@ private fun RBuilder.configEditorItem(props: ConfigEditorItemProps) {
|
|||||||
+TreeStyles.resizeableInput
|
+TreeStyles.resizeableInput
|
||||||
}
|
}
|
||||||
valueChooser(
|
valueChooser(
|
||||||
props.name,
|
itemName,
|
||||||
actualItem,
|
actualItem,
|
||||||
descriptorItem as? ValueDescriptor,
|
descriptorItem as? ValueDescriptor,
|
||||||
valueChanged
|
valueChanged
|
||||||
@ -190,63 +216,68 @@ private fun RBuilder.configEditorItem(props: ConfigEditorItemProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public external interface ConfigEditorProps : RProps {
|
|
||||||
public var id: Name
|
|
||||||
public var root: Config
|
|
||||||
public var default: Meta?
|
|
||||||
public var descriptor: NodeDescriptor?
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsExport
|
@JsExport
|
||||||
public val ConfigEditor: FunctionalComponent<ConfigEditorProps> = functionalComponent("ConfigEditor") { props ->
|
public val PropertyEditor: FunctionalComponent<PropertyEditorProps> = functionalComponent("PropertyEditor") { props ->
|
||||||
child(ConfigEditorItem) {
|
child(PropertyEditorItem) {
|
||||||
attrs {
|
attrs {
|
||||||
this.key = ""
|
this.key = ""
|
||||||
this.root = props.root
|
this.provider = props.provider
|
||||||
|
this.defaultProvider = props.defaultProvider
|
||||||
this.name = Name.EMPTY
|
this.name = Name.EMPTY
|
||||||
this.default = props.default
|
|
||||||
this.descriptor = props.descriptor
|
this.descriptor = props.descriptor
|
||||||
|
this.scope = props.scope
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public fun RBuilder.propertyEditor(
|
||||||
|
provider: MutableItemProvider,
|
||||||
|
defaultProvider: ItemProvider?,
|
||||||
|
updateFlow: Flow<Name>? = null,
|
||||||
|
descriptor: NodeDescriptor? = null,
|
||||||
|
scope: CoroutineScope? = null,
|
||||||
|
key: Any? = null,
|
||||||
|
) {
|
||||||
|
child(PropertyEditor) {
|
||||||
|
attrs {
|
||||||
|
this.provider = provider
|
||||||
|
this.defaultProvider = defaultProvider
|
||||||
|
this.updateFlow = updateFlow
|
||||||
|
this.descriptor = descriptor
|
||||||
|
this.key = key?.toString() ?: ""
|
||||||
|
this.scope = scope
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
private fun Config.flowUpdates(): Flow<Name> = callbackFlow {
|
||||||
|
onChange(this) { name, _, _ ->
|
||||||
|
launch {
|
||||||
|
send(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
awaitClose {
|
||||||
|
removeListener(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public fun RBuilder.configEditor(
|
||||||
|
config: Config,
|
||||||
|
default: ItemProvider? = null,
|
||||||
|
descriptor: NodeDescriptor? = null,
|
||||||
|
key: Any? = null,
|
||||||
|
scope: CoroutineScope? = null,
|
||||||
|
): Unit = propertyEditor(config, default, config.flowUpdates(), descriptor, scope, key = key)
|
||||||
|
|
||||||
public fun Element.configEditor(
|
public fun Element.configEditor(
|
||||||
config: Config,
|
config: Config,
|
||||||
descriptor: NodeDescriptor? = null,
|
descriptor: NodeDescriptor? = null,
|
||||||
default: Meta? = null,
|
default: Meta? = null,
|
||||||
key: Any? = null,
|
key: Any? = null,
|
||||||
) {
|
scope: CoroutineScope? = null,
|
||||||
render(this) {
|
): Unit = render(this) {
|
||||||
child(ConfigEditor) {
|
configEditor(config, default, descriptor, key, scope)
|
||||||
attrs {
|
|
||||||
this.key = key?.toString() ?: ""
|
|
||||||
this.root = config
|
|
||||||
this.descriptor = descriptor
|
|
||||||
this.default = default
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun RBuilder.configEditor(
|
|
||||||
config: Config,
|
|
||||||
descriptor: NodeDescriptor? = null,
|
|
||||||
default: Meta? = null,
|
|
||||||
key: Any? = null,
|
|
||||||
) {
|
|
||||||
child(ConfigEditor) {
|
|
||||||
attrs {
|
|
||||||
this.key = key?.toString() ?: ""
|
|
||||||
this.root = config
|
|
||||||
this.descriptor = descriptor
|
|
||||||
this.default = default
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//
|
|
||||||
//public fun RBuilder.configEditor(
|
|
||||||
// obj: Configurable,
|
|
||||||
// descriptor: NodeDescriptor?,
|
|
||||||
// default: Meta? = null,
|
|
||||||
// key: Any? = null
|
|
||||||
//): Unit = configEditor(obj.config, descriptor, default, key)
|
|
@ -37,8 +37,7 @@ public val ThreeCanvasComponent: FunctionalComponent<ThreeCanvasProps> = functio
|
|||||||
if (canvas == null) {
|
if (canvas == null) {
|
||||||
val element = elementRef.current as? HTMLElement ?: error("Canvas element not found")
|
val element = elementRef.current as? HTMLElement ?: error("Canvas element not found")
|
||||||
val three: ThreePlugin = props.context.plugins.fetch(ThreePlugin)
|
val three: ThreePlugin = props.context.plugins.fetch(ThreePlugin)
|
||||||
val newCanvas: ThreeCanvas =
|
val newCanvas: ThreeCanvas = three.createCanvas(element, props.options ?: Canvas3DOptions.empty())
|
||||||
three.createCanvas(element, props.options ?: Canvas3DOptions.empty())
|
|
||||||
props.canvasCallback?.invoke(newCanvas)
|
props.canvasCallback?.invoke(newCanvas)
|
||||||
canvas = newCanvas
|
canvas = newCanvas
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ public object TreeStyles : StyleSheet("treeStyles", true) {
|
|||||||
/**
|
/**
|
||||||
* Style the caret/arrow
|
* Style the caret/arrow
|
||||||
*/
|
*/
|
||||||
public val treeCaret by css {
|
public val treeCaret: RuleSet by css {
|
||||||
cursor = Cursor.pointer
|
cursor = Cursor.pointer
|
||||||
userSelect = UserSelect.none
|
userSelect = UserSelect.none
|
||||||
/* Create the caret/arrow with a unicode, and style it */
|
/* Create the caret/arrow with a unicode, and style it */
|
||||||
@ -32,7 +32,7 @@ public object TreeStyles : StyleSheet("treeStyles", true) {
|
|||||||
/**
|
/**
|
||||||
* Rotate the caret/arrow icon when clicked on (using JavaScript)
|
* Rotate the caret/arrow icon when clicked on (using JavaScript)
|
||||||
*/
|
*/
|
||||||
val treeCaredDown by css {
|
public val treeCaredDown:RuleSet by css {
|
||||||
before {
|
before {
|
||||||
content = "\u25B6".quoted
|
content = "\u25B6".quoted
|
||||||
color = Color.black
|
color = Color.black
|
||||||
@ -42,7 +42,7 @@ public object TreeStyles : StyleSheet("treeStyles", true) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val treeItem by css {
|
public val treeItem:RuleSet by css {
|
||||||
alignItems = Align.center
|
alignItems = Align.center
|
||||||
paddingLeft = 10.px
|
paddingLeft = 10.px
|
||||||
borderLeftStyle = BorderStyle.dashed
|
borderLeftStyle = BorderStyle.dashed
|
||||||
@ -53,27 +53,27 @@ public object TreeStyles : StyleSheet("treeStyles", true) {
|
|||||||
borderBottomColor = Color.lightGray
|
borderBottomColor = Color.lightGray
|
||||||
}
|
}
|
||||||
|
|
||||||
val treeLeaf by css {
|
public val treeLeaf:RuleSet by css {
|
||||||
display = Display.flex
|
display = Display.flex
|
||||||
flexDirection = FlexDirection.row
|
flexDirection = FlexDirection.row
|
||||||
flexWrap = FlexWrap.nowrap
|
flexWrap = FlexWrap.nowrap
|
||||||
//alignItems = Align.center
|
//alignItems = Align.center
|
||||||
}
|
}
|
||||||
|
|
||||||
val treeLabel by css {
|
public val treeLabel:RuleSet by css {
|
||||||
overflow = Overflow.hidden
|
overflow = Overflow.hidden
|
||||||
flex(flexGrow = 1.0, flexShrink = 1.0)
|
flex(flexGrow = 1.0, flexShrink = 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
val treeLabelInactive by css {
|
public val treeLabelInactive: RuleSet by css {
|
||||||
color = Color.lightGray
|
color = Color.lightGray
|
||||||
}
|
}
|
||||||
|
|
||||||
val treeLabelSelected by css {
|
public val treeLabelSelected:RuleSet by css {
|
||||||
backgroundColor = Color.lightBlue
|
backgroundColor = Color.lightBlue
|
||||||
}
|
}
|
||||||
|
|
||||||
val linkButton by css {
|
public val linkButton:RuleSet by css {
|
||||||
backgroundColor = Color.white
|
backgroundColor = Color.white
|
||||||
border = "none"
|
border = "none"
|
||||||
padding(left = 4.pt, right = 4.pt, top = 0.pt, bottom = 0.pt)
|
padding(left = 4.pt, right = 4.pt, top = 0.pt, bottom = 0.pt)
|
||||||
@ -86,7 +86,7 @@ public object TreeStyles : StyleSheet("treeStyles", true) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val removeButton by css {
|
public val removeButton:RuleSet by css {
|
||||||
backgroundColor = Color.white
|
backgroundColor = Color.white
|
||||||
borderStyle = BorderStyle.solid
|
borderStyle = BorderStyle.solid
|
||||||
borderRadius = 2.px
|
borderRadius = 2.px
|
||||||
@ -104,7 +104,7 @@ public object TreeStyles : StyleSheet("treeStyles", true) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val resizeableInput by css {
|
public val resizeableInput: RuleSet by css {
|
||||||
overflow = Overflow.hidden
|
overflow = Overflow.hidden
|
||||||
maxWidth = 120.pt
|
maxWidth = 120.pt
|
||||||
flex(flexGrow = 2.0, flexShrink = 2.0, flexBasis = 60.pt)
|
flex(flexGrow = 2.0, flexShrink = 2.0, flexBasis = 60.pt)
|
||||||
|
@ -9,142 +9,166 @@ import hep.dataforge.vision.widgetType
|
|||||||
import kotlinx.html.InputType
|
import kotlinx.html.InputType
|
||||||
import kotlinx.html.js.onChangeFunction
|
import kotlinx.html.js.onChangeFunction
|
||||||
import kotlinx.html.js.onKeyDownFunction
|
import kotlinx.html.js.onKeyDownFunction
|
||||||
import org.w3c.dom.HTMLElement
|
|
||||||
import org.w3c.dom.HTMLInputElement
|
import org.w3c.dom.HTMLInputElement
|
||||||
import org.w3c.dom.HTMLSelectElement
|
import org.w3c.dom.HTMLSelectElement
|
||||||
import org.w3c.dom.events.Event
|
import org.w3c.dom.events.Event
|
||||||
import react.*
|
import react.*
|
||||||
import react.dom.defaultValue
|
|
||||||
import react.dom.option
|
import react.dom.option
|
||||||
import styled.styledInput
|
import styled.styledInput
|
||||||
import styled.styledSelect
|
import styled.styledSelect
|
||||||
|
|
||||||
public external interface ValueChooserProps : RProps {
|
public external interface ValueChooserProps : RProps {
|
||||||
var item: MetaItem<*>?
|
public var item: MetaItem?
|
||||||
var descriptor: ValueDescriptor?
|
public var descriptor: ValueDescriptor?
|
||||||
var valueChanged: ((Value?) -> Unit)?
|
public var valueChanged: ((Value?) -> Unit)?
|
||||||
}
|
|
||||||
|
|
||||||
public external interface ValueChooserState : RState {
|
|
||||||
var rawInput: Boolean?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsExport
|
@JsExport
|
||||||
class ValueChooserComponent(props: ValueChooserProps) : RComponent<ValueChooserProps, ValueChooserState>(props) {
|
public val StringValueChooser: FunctionalComponent<ValueChooserProps> =
|
||||||
private val element = createRef<HTMLElement>()
|
functionalComponent("StringValueChooser") { props ->
|
||||||
|
var value by useState(props.item.string ?: "")
|
||||||
private fun getValue(): Value? {
|
val keyDown: (Event) -> Unit = { event ->
|
||||||
val element = element.current ?: return null//state.element ?: return null
|
if (event.type == "keydown" && event.asDynamic().key == "Enter") {
|
||||||
return when (element) {
|
value = (event.target as HTMLInputElement).value
|
||||||
is HTMLInputElement -> if (element.type == "checkbox") {
|
if(value!= props.item.string) {
|
||||||
if (element.checked) True else False
|
props.valueChanged?.invoke(value.asValue())
|
||||||
} else {
|
}
|
||||||
element.value.asValue()
|
}
|
||||||
|
}
|
||||||
|
val handleChange: (Event) -> Unit = {
|
||||||
|
value = (it.target as HTMLInputElement).value
|
||||||
|
}
|
||||||
|
styledInput(type = InputType.text) {
|
||||||
|
attrs {
|
||||||
|
this.value = value
|
||||||
|
onKeyDownFunction = keyDown
|
||||||
|
onChangeFunction = handleChange
|
||||||
}
|
}
|
||||||
is HTMLSelectElement -> element.value.asValue()
|
|
||||||
else -> error("Unknown event target: $element")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val commit: (Event) -> Unit = { _ ->
|
@JsExport
|
||||||
props.valueChanged?.invoke(getValue())
|
public val BooleanValueChooser: FunctionalComponent<ValueChooserProps> =
|
||||||
}
|
functionalComponent("BooleanValueChooser") { props ->
|
||||||
|
var checkedValue by useState(props.item.boolean ?: false)
|
||||||
private val keyDown: (Event) -> Unit = { event ->
|
val handleChange: (Event) -> Unit = {
|
||||||
if (event.type == "keydown" && event.asDynamic().key == "Enter") {
|
val newValue = (it.target as HTMLInputElement).checked
|
||||||
commit(event)
|
checkedValue = newValue
|
||||||
|
props.valueChanged?.invoke(newValue.asValue())
|
||||||
|
}
|
||||||
|
styledInput(type = InputType.checkBox) {
|
||||||
|
attrs {
|
||||||
|
this.attributes["indeterminate"] = (checkedValue == null).toString()
|
||||||
|
checked = checkedValue
|
||||||
|
onChangeFunction = handleChange
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun shouldComponentUpdate(
|
@JsExport
|
||||||
nextProps: ValueChooserProps,
|
public val NumberValueChooser: FunctionalComponent<ValueChooserProps> =
|
||||||
nextState: ValueChooserState
|
functionalComponent("NumberValueChooser") { props ->
|
||||||
): Boolean = nextProps.item !== props.item
|
var value by useState(props.item.string ?: "")
|
||||||
|
val keyDown: (Event) -> Unit = { event ->
|
||||||
override fun componentDidUpdate(prevProps: ValueChooserProps, prevState: ValueChooserState, snapshot: Any) {
|
if (event.type == "keydown" && event.asDynamic().key == "Enter") {
|
||||||
(element.current as? HTMLInputElement)?.let { element ->
|
value = (event.target as HTMLInputElement).value
|
||||||
if (element.type == "checkbox") {
|
val number = value.toDoubleOrNull()
|
||||||
element.defaultChecked = props.item?.boolean ?: false
|
if (number == null) {
|
||||||
} else {
|
console.error("The input value $value is not a number")
|
||||||
element.defaultValue = props.item?.string ?: ""
|
} else {
|
||||||
|
props.valueChanged?.invoke(number.asValue())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val handleChange: (Event) -> Unit = {
|
||||||
|
value = (it.target as HTMLInputElement).value
|
||||||
|
}
|
||||||
|
styledInput(type = InputType.number) {
|
||||||
|
attrs {
|
||||||
|
this.value = value
|
||||||
|
onKeyDownFunction = keyDown
|
||||||
|
onChangeFunction = handleChange
|
||||||
|
props.descriptor?.attributes?.get("step").string?.let {
|
||||||
|
step = it
|
||||||
|
}
|
||||||
|
props.descriptor?.attributes?.get("min").string?.let {
|
||||||
|
min = it
|
||||||
|
}
|
||||||
|
props.descriptor?.attributes?.get("max").string?.let {
|
||||||
|
max = it
|
||||||
|
}
|
||||||
}
|
}
|
||||||
element.indeterminate = props.item == null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun RBuilder.stringInput() = styledInput(type = InputType.text) {
|
@JsExport
|
||||||
attrs {
|
public val ComboValueChooser: FunctionalComponent<ValueChooserProps> =
|
||||||
this.defaultValue = props.item?.string ?: ""
|
functionalComponent("ComboValueChooser") { props ->
|
||||||
onKeyDownFunction = keyDown
|
var selected by useState(props.item.string ?: "")
|
||||||
|
val handleChange: (Event) -> Unit = {
|
||||||
|
selected = (it.target as HTMLSelectElement).value
|
||||||
|
props.valueChanged?.invoke(selected.asValue())
|
||||||
|
}
|
||||||
|
styledSelect {
|
||||||
|
props.descriptor?.allowedValues?.forEach {
|
||||||
|
option {
|
||||||
|
+it.string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
attrs {
|
||||||
|
this.value = props.item?.string ?: ""
|
||||||
|
multiple = false
|
||||||
|
onChangeFunction = handleChange
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ref = element
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun RBuilder.render() {
|
@JsExport
|
||||||
val descriptor = props.descriptor
|
public val ColorValueChooser: FunctionalComponent<ValueChooserProps> =
|
||||||
val type = descriptor?.type?.firstOrNull()
|
functionalComponent("ColorValueChooser") { props ->
|
||||||
when {
|
var value by useState(
|
||||||
state.rawInput == true -> stringInput()
|
props.item.value?.let { value ->
|
||||||
descriptor?.widgetType == "color" -> styledInput(type = InputType.color) {
|
if (value.type == ValueType.NUMBER) Colors.rgbToString(value.int)
|
||||||
ref = element
|
else value.string
|
||||||
attrs {
|
} ?: "#000000"
|
||||||
this.defaultValue = props.item?.value?.let { value ->
|
)
|
||||||
if (value.type == ValueType.NUMBER) Colors.rgbToString(value.int)
|
val handleChange: (Event) -> Unit = {
|
||||||
else value.string
|
value = (it.target as HTMLInputElement).value
|
||||||
} ?: "#000000"
|
props.valueChanged?.invoke(value.asValue())
|
||||||
onChangeFunction = commit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
type == ValueType.BOOLEAN -> {
|
|
||||||
styledInput(type = InputType.checkBox) {
|
|
||||||
ref = element
|
|
||||||
attrs {
|
|
||||||
defaultChecked = props.item?.boolean ?: false
|
|
||||||
onChangeFunction = commit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
type == ValueType.NUMBER -> styledInput(type = InputType.number) {
|
|
||||||
ref = element
|
|
||||||
attrs {
|
|
||||||
descriptor.attributes["step"].string?.let {
|
|
||||||
step = it
|
|
||||||
}
|
|
||||||
descriptor.attributes["min"].string?.let {
|
|
||||||
min = it
|
|
||||||
}
|
|
||||||
descriptor.attributes["max"].string?.let {
|
|
||||||
max = it
|
|
||||||
}
|
|
||||||
defaultValue = props.item?.string ?: ""
|
|
||||||
onKeyDownFunction = keyDown
|
|
||||||
}
|
|
||||||
}
|
|
||||||
descriptor?.allowedValues?.isNotEmpty() ?: false -> styledSelect {
|
|
||||||
descriptor!!.allowedValues.forEach {
|
|
||||||
option {
|
|
||||||
+it.string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ref = element
|
|
||||||
attrs {
|
|
||||||
this.value = props.item?.string ?: ""
|
|
||||||
multiple = false
|
|
||||||
onChangeFunction = commit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> stringInput()
|
|
||||||
}
|
}
|
||||||
|
styledInput(type = InputType.color) {
|
||||||
|
attrs {
|
||||||
|
this.value = value
|
||||||
|
onChangeFunction = handleChange
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsExport
|
||||||
|
public val ValueChooser: FunctionalComponent<ValueChooserProps> = functionalComponent("ValueChooser") { props ->
|
||||||
|
val rawInput by useState(false)
|
||||||
|
|
||||||
|
val descriptor = props.descriptor
|
||||||
|
val type = descriptor?.type?.firstOrNull()
|
||||||
|
|
||||||
|
when {
|
||||||
|
rawInput -> child(StringValueChooser, props)
|
||||||
|
descriptor?.widgetType == "color" -> child(ColorValueChooser, props)
|
||||||
|
type == ValueType.BOOLEAN -> child(BooleanValueChooser, props)
|
||||||
|
type == ValueType.NUMBER -> child(NumberValueChooser, props)
|
||||||
|
descriptor?.allowedValues?.isNotEmpty() ?: false -> child(ComboValueChooser, props)
|
||||||
|
//TODO handle lists
|
||||||
|
else -> child(StringValueChooser, props)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun RBuilder.valueChooser(
|
internal fun RBuilder.valueChooser(
|
||||||
name: Name,
|
name: Name,
|
||||||
item: MetaItem<*>?,
|
item: MetaItem?,
|
||||||
descriptor: ValueDescriptor? = null,
|
descriptor: ValueDescriptor? = null,
|
||||||
callback: (Value?) -> Unit
|
callback: (Value?) -> Unit,
|
||||||
) {
|
) {
|
||||||
child(ValueChooserComponent::class) {
|
child(ValueChooser) {
|
||||||
attrs {
|
attrs {
|
||||||
key = name.toString()
|
key = name.toString()
|
||||||
this.item = item
|
this.item = item
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
package hep.dataforge.vision
|
package hep.dataforge.vision
|
||||||
|
|
||||||
import hep.dataforge.meta.MetaItem
|
import hep.dataforge.meta.*
|
||||||
import hep.dataforge.meta.get
|
|
||||||
import hep.dataforge.meta.number
|
|
||||||
import hep.dataforge.values.ValueType
|
import hep.dataforge.values.ValueType
|
||||||
import hep.dataforge.values.int
|
import hep.dataforge.values.int
|
||||||
import hep.dataforge.values.string
|
import hep.dataforge.values.string
|
||||||
@ -192,9 +190,9 @@ public object Colors {
|
|||||||
/**
|
/**
|
||||||
* Convert color represented as Meta to string of format #rrggbb
|
* Convert color represented as Meta to string of format #rrggbb
|
||||||
*/
|
*/
|
||||||
fun fromMeta(item: MetaItem<*>): String {
|
fun fromMeta(item: MetaItem): String {
|
||||||
return when (item) {
|
return when (item) {
|
||||||
is MetaItem.NodeItem<*> -> {
|
is NodeItem -> {
|
||||||
val node = item.node
|
val node = item.node
|
||||||
rgbToString(
|
rgbToString(
|
||||||
node[RED_KEY].number?.toByte()?.toUByte() ?: 0u,
|
node[RED_KEY].number?.toByte()?.toUByte() ?: 0u,
|
||||||
@ -202,7 +200,7 @@ public object Colors {
|
|||||||
node[BLUE_KEY].number?.toByte()?.toUByte() ?: 0u
|
node[BLUE_KEY].number?.toByte()?.toUByte() ?: 0u
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is MetaItem.ValueItem -> {
|
is ValueItem -> {
|
||||||
if (item.value.type == ValueType.NUMBER) {
|
if (item.value.type == ValueType.NUMBER) {
|
||||||
rgbToString(item.value.int)
|
rgbToString(item.value.int)
|
||||||
} else {
|
} else {
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
package hep.dataforge.vision
|
||||||
|
|
||||||
|
import hep.dataforge.meta.DFExperimental
|
||||||
|
import hep.dataforge.meta.Meta
|
||||||
|
import hep.dataforge.meta.MetaBuilder
|
||||||
|
import kotlin.properties.ReadOnlyProperty
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A reference to a style defined in a specific container
|
||||||
|
*/
|
||||||
|
public class StyleReference(public val owner: VisionGroup, public val name: String)
|
||||||
|
|
||||||
|
private tailrec fun styleIsDefined(vision: Vision, reference: StyleReference): Boolean = when {
|
||||||
|
reference.owner === vision -> true
|
||||||
|
vision.parent == null -> false
|
||||||
|
else -> styleIsDefined(vision.parent!!, reference)
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisionBuilder
|
||||||
|
public fun Vision.useStyle(reference: StyleReference) {
|
||||||
|
//check that style is defined in a parent
|
||||||
|
//check(styleIsDefined(this, reference)) { "Style reference does not belong to a Vision parent" }
|
||||||
|
useStyle(reference.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
@DFExperimental
|
||||||
|
@VisionBuilder
|
||||||
|
public fun VisionGroup.style(builder: MetaBuilder.() -> Unit): ReadOnlyProperty<Any?, StyleReference> =
|
||||||
|
ReadOnlyProperty { _, property ->
|
||||||
|
val styleName = property.name
|
||||||
|
styleSheet.define(styleName, Meta(builder))
|
||||||
|
StyleReference(this, styleName)
|
||||||
|
}
|
@ -5,13 +5,14 @@ import hep.dataforge.names.Name
|
|||||||
import hep.dataforge.names.NameToken
|
import hep.dataforge.names.NameToken
|
||||||
import hep.dataforge.names.asName
|
import hep.dataforge.names.asName
|
||||||
import hep.dataforge.names.plus
|
import hep.dataforge.names.plus
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A container for styles
|
* A container for styles
|
||||||
*/
|
*/
|
||||||
public inline class StyleSheet(private val owner: VisionGroup) {
|
public inline class StyleSheet(private val owner: VisionGroup) {
|
||||||
|
|
||||||
private val styleNode get() = owner.properties?.get(STYLESHEET_KEY).node
|
private val styleNode get() = owner.getOwnProperty(STYLESHEET_KEY).node
|
||||||
|
|
||||||
public val items: Map<NameToken, Meta>? get() = styleNode?.items?.mapValues { it.value.node ?: Meta.EMPTY }
|
public val items: Map<NameToken, Meta>? get() = styleNode?.items?.mapValues { it.value.node ?: Meta.EMPTY }
|
||||||
|
|
||||||
@ -21,11 +22,7 @@ public inline class StyleSheet(private val owner: VisionGroup) {
|
|||||||
* Define a style without notifying owner
|
* Define a style without notifying owner
|
||||||
*/
|
*/
|
||||||
public fun define(key: String, style: Meta?) {
|
public fun define(key: String, style: Meta?) {
|
||||||
if (style == null) {
|
owner.setProperty(STYLESHEET_KEY + key, style)
|
||||||
styleNode?.remove(key)
|
|
||||||
} else {
|
|
||||||
owner.config[STYLESHEET_KEY + key] = style
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -43,7 +40,7 @@ public inline class StyleSheet(private val owner: VisionGroup) {
|
|||||||
* Create and set a style
|
* Create and set a style
|
||||||
*/
|
*/
|
||||||
public operator fun set(key: String, builder: MetaBuilder.() -> Unit) {
|
public operator fun set(key: String, builder: MetaBuilder.() -> Unit) {
|
||||||
val newStyle = get(key)?.edit(builder) ?: Meta(builder)
|
val newStyle = get(key)?.builder()?.apply(builder) ?: Meta(builder)
|
||||||
set(key, newStyle.seal())
|
set(key, newStyle.seal())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,7 +55,9 @@ internal fun Vision.styleChanged(key: String, oldStyle: Meta?, newStyle: Meta?)
|
|||||||
val tokens: Collection<Name> =
|
val tokens: Collection<Name> =
|
||||||
((oldStyle?.items?.keys ?: emptySet()) + (newStyle?.items?.keys ?: emptySet()))
|
((oldStyle?.items?.keys ?: emptySet()) + (newStyle?.items?.keys ?: emptySet()))
|
||||||
.map { it.asName() }
|
.map { it.asName() }
|
||||||
tokens.forEach { parent?.propertyChanged(it) }
|
parent?.scope?.launch {
|
||||||
|
tokens.forEach { parent?.notifyPropertyChanged(it) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (this is VisionGroup) {
|
if (this is VisionGroup) {
|
||||||
for (obj in this) {
|
for (obj in this) {
|
||||||
@ -68,6 +67,15 @@ internal fun Vision.styleChanged(key: String, oldStyle: Meta?, newStyle: Meta?)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of names of styles applied to this object. Order matters. Not inherited.
|
||||||
|
*/
|
||||||
|
public var Vision.styles: List<String>
|
||||||
|
get() = getOwnProperty(Vision.STYLE_KEY)?.stringList ?: emptyList()
|
||||||
|
set(value) {
|
||||||
|
setProperty(Vision.STYLE_KEY, value)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A stylesheet for this group and its descendants. Stylesheet is not applied directly,
|
* A stylesheet for this group and its descendants. Stylesheet is not applied directly,
|
||||||
* but instead is just a repository for named configurations.
|
* but instead is just a repository for named configurations.
|
||||||
@ -78,7 +86,7 @@ public val VisionGroup.styleSheet: StyleSheet get() = StyleSheet(this)
|
|||||||
* Add style name to the list of styles to be resolved later. The style with given name does not necessary exist at the moment.
|
* Add style name to the list of styles to be resolved later. The style with given name does not necessary exist at the moment.
|
||||||
*/
|
*/
|
||||||
public fun Vision.useStyle(name: String) {
|
public fun Vision.useStyle(name: String) {
|
||||||
styles = (properties[Vision.STYLE_KEY]?.stringList ?: emptyList()) + name
|
styles = (getOwnProperty(Vision.STYLE_KEY)?.stringList ?: emptyList()) + name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -86,12 +94,12 @@ public fun Vision.useStyle(name: String) {
|
|||||||
* Find a style with given name for given [Vision]. The style is not necessary applied to this [Vision].
|
* Find a style with given name for given [Vision]. The style is not necessary applied to this [Vision].
|
||||||
*/
|
*/
|
||||||
public tailrec fun Vision.getStyle(name: String): Meta? =
|
public tailrec fun Vision.getStyle(name: String): Meta? =
|
||||||
properties?.get(StyleSheet.STYLESHEET_KEY + name).node ?: parent?.getStyle(name)
|
getOwnProperty(StyleSheet.STYLESHEET_KEY + name).node ?: parent?.getStyle(name)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve an item in all style layers
|
* Resolve an item in all style layers
|
||||||
*/
|
*/
|
||||||
public fun Vision.getStyleItems(name: Name): Sequence<MetaItem<*>> {
|
public fun Vision.getStyleItems(name: Name): Sequence<MetaItem> {
|
||||||
return styles.asSequence().map {
|
return styles.asSequence().map {
|
||||||
getStyle(it)
|
getStyle(it)
|
||||||
}.map {
|
}.map {
|
||||||
@ -103,3 +111,5 @@ public fun Vision.getStyleItems(name: Name): Sequence<MetaItem<*>> {
|
|||||||
* Collect all styles for this object in a single laminate
|
* Collect all styles for this object in a single laminate
|
||||||
*/
|
*/
|
||||||
public val Vision.allStyles: Laminate get() = Laminate(styles.mapNotNull(::getStyle))
|
public val Vision.allStyles: Laminate get() = Laminate(styles.mapNotNull(::getStyle))
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,22 +1,28 @@
|
|||||||
package hep.dataforge.vision
|
package hep.dataforge.vision
|
||||||
|
|
||||||
import hep.dataforge.meta.*
|
import hep.dataforge.meta.DFExperimental
|
||||||
|
import hep.dataforge.meta.Meta
|
||||||
|
import hep.dataforge.meta.MetaItem
|
||||||
|
import hep.dataforge.meta.MutableItemProvider
|
||||||
import hep.dataforge.meta.descriptors.Described
|
import hep.dataforge.meta.descriptors.Described
|
||||||
import hep.dataforge.meta.descriptors.NodeDescriptor
|
import hep.dataforge.meta.descriptors.NodeDescriptor
|
||||||
|
import hep.dataforge.meta.descriptors.get
|
||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.Name
|
||||||
import hep.dataforge.names.asName
|
import hep.dataforge.names.asName
|
||||||
import hep.dataforge.names.toName
|
import hep.dataforge.names.toName
|
||||||
import hep.dataforge.provider.Type
|
import hep.dataforge.provider.Type
|
||||||
import hep.dataforge.values.asValue
|
|
||||||
import hep.dataforge.vision.Vision.Companion.TYPE
|
import hep.dataforge.vision.Vision.Companion.TYPE
|
||||||
import hep.dataforge.vision.Vision.Companion.VISIBLE_KEY
|
import kotlinx.coroutines.*
|
||||||
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.callbackFlow
|
||||||
import kotlinx.serialization.Transient
|
import kotlinx.serialization.Transient
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A root type for display hierarchy
|
* A root type for display hierarchy
|
||||||
*/
|
*/
|
||||||
@Type(TYPE)
|
@Type(TYPE)
|
||||||
public interface Vision : Configurable, Described {
|
public interface Vision : Described {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The parent object of this one. If null, this one is a root.
|
* The parent object of this one. If null, this one is a root.
|
||||||
@ -25,46 +31,69 @@ public interface Vision : Configurable, Described {
|
|||||||
public var parent: VisionGroup?
|
public var parent: VisionGroup?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Nullable version of [config] used to check if this [Vision] has custom properties
|
* Properties belonging to this [Vision] potentially including artificial properties
|
||||||
*/
|
*/
|
||||||
public val properties: Config?
|
@Transient
|
||||||
|
public val meta: Meta
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All properties including styles and prototypes if present, including inherited ones
|
* A coroutine scope for asynchronous calls and locks
|
||||||
*/
|
*/
|
||||||
public val allProperties: Laminate
|
public val scope: CoroutineScope get() = parent?.scope ?: GlobalScope
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get property (including styles). [inherit] toggles parent node property lookup
|
* A fast accessor method to get own property (no inheritance or styles).
|
||||||
|
* Should be equivalent to `getProperty(name,false,false,false)`.
|
||||||
*/
|
*/
|
||||||
public fun getProperty(name: Name, inherit: Boolean = true): MetaItem<*>?
|
public fun getOwnProperty(name: Name): MetaItem?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Trigger property invalidation event. If [name] is empty, notify that the whole object is changed
|
* Get property.
|
||||||
|
* @param inherit toggles parent node property lookup. Null means inference from descriptor. Default is false.
|
||||||
|
* @param includeStyles toggles inclusion of. Null means inference from descriptor. Default is true.
|
||||||
*/
|
*/
|
||||||
public fun propertyChanged(name: Name): Unit
|
public fun getProperty(
|
||||||
|
name: Name,
|
||||||
|
inherit: Boolean = false,
|
||||||
|
includeStyles: Boolean = true,
|
||||||
|
includeDefaults: Boolean = true,
|
||||||
|
): MetaItem?
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add listener triggering on property change
|
* Set the property value
|
||||||
*/
|
*/
|
||||||
public fun onPropertyChange(owner: Any?, action: (Name) -> Unit): Unit
|
public fun setProperty(name: Name, item: MetaItem?, notify: Boolean = true)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove change listeners with given owner.
|
* Subscribe on property updates. The subscription is bound to the given [scope] and canceled when the scope is canceled
|
||||||
*/
|
*/
|
||||||
public fun removeChangeListener(owner: Any?)
|
public fun onPropertyChange(scope: CoroutineScope, callback: suspend (Name) -> Unit)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of names of styles applied to this object. Order matters. Not inherited.
|
* Flow of property invalidation events. It does not contain property values after invalidation since it is not clear
|
||||||
|
* if it should include inherited properties etc.
|
||||||
*/
|
*/
|
||||||
public var styles: List<String>
|
@DFExperimental
|
||||||
get() = properties[STYLE_KEY]?.stringList ?: emptyList()
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
set(value) {
|
public val propertyChanges: Flow<Name>
|
||||||
config[STYLE_KEY] = value
|
get() = callbackFlow<Name> {
|
||||||
|
coroutineScope {
|
||||||
|
onPropertyChange(this) {
|
||||||
|
send(it)
|
||||||
|
}
|
||||||
|
awaitClose { cancel() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update this vision using external meta. Children are not updated.
|
* Notify all listeners that a property has been changed and should be invalidated
|
||||||
|
*/
|
||||||
|
public suspend fun notifyPropertyChanged(propertyName: Name): Unit
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update this vision using a dif represented by [VisionChange].
|
||||||
*/
|
*/
|
||||||
public fun update(change: VisionChange)
|
public fun update(change: VisionChange)
|
||||||
|
|
||||||
@ -78,60 +107,61 @@ public interface Vision : Configurable, Described {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public fun Vision.asyncNotifyPropertyChange(propertyName: Name) {
|
||||||
|
scope.launch {
|
||||||
|
notifyPropertyChanged(propertyName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Own properties, excluding inheritance, styles and descriptor
|
||||||
|
*/
|
||||||
|
public val Vision.ownProperties: MutableItemProvider
|
||||||
|
get() = object : MutableItemProvider {
|
||||||
|
override fun getItem(name: Name): MetaItem? = getOwnProperty(name)
|
||||||
|
override fun setItem(name: Name, item: MetaItem?): Unit = setProperty(name, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenient accessor for all properties of a vision.
|
||||||
|
* @param inherit - inherit property value from the parent by default. If null, inheritance is inferred from descriptor
|
||||||
|
*/
|
||||||
|
public fun Vision.allProperties(
|
||||||
|
inherit: Boolean? = null,
|
||||||
|
includeStyles: Boolean? = null,
|
||||||
|
includeDefaults: Boolean = true,
|
||||||
|
): MutableItemProvider = object : MutableItemProvider {
|
||||||
|
override fun getItem(name: Name): MetaItem? = getProperty(
|
||||||
|
name,
|
||||||
|
inherit = inherit ?: (descriptor?.get(name)?.inherited != false),
|
||||||
|
includeStyles = includeStyles ?: (descriptor?.get(name)?.usesStyles == true),
|
||||||
|
includeDefaults = includeDefaults
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun setItem(name: Name, item: MetaItem?): Unit = setProperty(name, item)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get [Vision] property using key as a String
|
* Get [Vision] property using key as a String
|
||||||
*/
|
*/
|
||||||
public fun Vision.getProperty(key: String, inherit: Boolean = true): MetaItem<*>? =
|
public fun Vision.getProperty(
|
||||||
getProperty(key.toName(), inherit)
|
key: String,
|
||||||
|
inherit: Boolean = false,
|
||||||
|
includeStyles: Boolean = true,
|
||||||
|
includeDefaults: Boolean = true,
|
||||||
|
): MetaItem? = getProperty(key.toName(), inherit, includeStyles, includeDefaults)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A convenience method to pair [getProperty]
|
* A convenience method to pair [getProperty]
|
||||||
*/
|
*/
|
||||||
public fun Vision.setProperty(key: Name, value: Any?) {
|
public fun Vision.setProperty(key: Name, item: Any?) {
|
||||||
config[key] = value
|
setProperty(key, MetaItem.of(item))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A convenience method to pair [getProperty]
|
* A convenience method to pair [getProperty]
|
||||||
*/
|
*/
|
||||||
public fun Vision.setProperty(key: String, value: Any?) {
|
public fun Vision.setProperty(key: String, item: Any?) {
|
||||||
config[key] = value
|
setProperty(key.toName(), MetaItem.of(item))
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Control visibility of the element
|
|
||||||
*/
|
|
||||||
public var Vision.visible: Boolean?
|
|
||||||
get() = getProperty(VISIBLE_KEY).boolean
|
|
||||||
set(value) = config.setValue(VISIBLE_KEY, value?.asValue())
|
|
||||||
|
|
||||||
///**
|
|
||||||
// * Convenience delegate for properties
|
|
||||||
// */
|
|
||||||
//public fun Vision.property(
|
|
||||||
// default: MetaItem<*>? = null,
|
|
||||||
// key: Name? = null,
|
|
||||||
// inherit: Boolean = true,
|
|
||||||
//): MutableItemDelegate =
|
|
||||||
// object : ReadWriteProperty<Any?, MetaItem<*>?> {
|
|
||||||
// override fun getValue(thisRef: Any?, property: KProperty<*>): MetaItem<*>? {
|
|
||||||
// val name = key ?: property.name.toName()
|
|
||||||
// return getProperty(name, inherit) ?: default
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// override fun setValue(thisRef: Any?, property: KProperty<*>, value: MetaItem<*>?) {
|
|
||||||
// val name = key ?: property.name.toName()
|
|
||||||
// setProperty(name, value)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
public fun Vision.props(inherit: Boolean = true): MutableItemProvider = object : MutableItemProvider {
|
|
||||||
override fun getItem(name: Name): MetaItem<*>? {
|
|
||||||
return getProperty(name, inherit)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setItem(name: Name, item: MetaItem<*>?) {
|
|
||||||
setProperty(name, item)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
@ -3,108 +3,134 @@ package hep.dataforge.vision
|
|||||||
import hep.dataforge.meta.*
|
import hep.dataforge.meta.*
|
||||||
import hep.dataforge.meta.descriptors.NodeDescriptor
|
import hep.dataforge.meta.descriptors.NodeDescriptor
|
||||||
import hep.dataforge.meta.descriptors.defaultItem
|
import hep.dataforge.meta.descriptors.defaultItem
|
||||||
import hep.dataforge.meta.descriptors.defaultMeta
|
|
||||||
import hep.dataforge.meta.descriptors.get
|
import hep.dataforge.meta.descriptors.get
|
||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.Name
|
||||||
import hep.dataforge.names.asName
|
import hep.dataforge.names.asName
|
||||||
|
import hep.dataforge.names.plus
|
||||||
|
import hep.dataforge.values.Null
|
||||||
import hep.dataforge.values.ValueType
|
import hep.dataforge.values.ValueType
|
||||||
import hep.dataforge.vision.Vision.Companion.STYLE_KEY
|
import hep.dataforge.vision.Vision.Companion.STYLE_KEY
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.Transient
|
import kotlinx.serialization.Transient
|
||||||
|
import kotlin.jvm.Synchronized
|
||||||
|
|
||||||
internal data class PropertyListener(
|
internal data class PropertyListener(
|
||||||
val owner: Any? = null,
|
val owner: Any? = null,
|
||||||
val action: (name: Name) -> Unit,
|
val action: (name: Name) -> Unit,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A full base implementation for a [Vision]
|
||||||
|
* @param properties Object own properties excluding styles and inheritance
|
||||||
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
@SerialName("vision")
|
@SerialName("vision")
|
||||||
public open class VisionBase : Vision {
|
public open class VisionBase(internal var properties: Config? = null) : Vision {
|
||||||
|
|
||||||
|
init {
|
||||||
|
//used during deserialization only
|
||||||
|
properties?.onChange(this) { name, oldItem, newItem ->
|
||||||
|
if (oldItem != newItem) {
|
||||||
|
scope.launch {
|
||||||
|
notifyPropertyChanged(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Transient
|
@Transient
|
||||||
override var parent: VisionGroup? = null
|
override var parent: VisionGroup? = null
|
||||||
|
|
||||||
/**
|
override val meta: Meta get() = properties ?: Meta.EMPTY
|
||||||
* Object own properties excluding styles and inheritance
|
|
||||||
*/
|
|
||||||
override var properties: Config? = null
|
|
||||||
protected set
|
|
||||||
|
|
||||||
override val descriptor: NodeDescriptor? get() = null
|
@Synchronized
|
||||||
|
protected fun getOrCreateConfig(): Config {
|
||||||
protected fun updateStyles(names: List<String>) {
|
if (properties == null) {
|
||||||
names.mapNotNull { getStyle(it) }.asSequence()
|
val newProperties = Config()
|
||||||
.flatMap { it.items.asSequence() }
|
newProperties.onChange(this) { name, oldItem, newItem ->
|
||||||
.distinctBy { it.key }
|
if (oldItem != newItem) {
|
||||||
.forEach {
|
scope.launch {
|
||||||
propertyChanged(it.key.asName())
|
notifyPropertyChanged(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
properties = newProperties
|
||||||
|
}
|
||||||
|
return properties!!
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The config is initialized and assigned on-demand.
|
* A fast accessor method to get own property (no inheritance or styles
|
||||||
* To avoid unnecessary allocations, one should access [getAllProperties] via [getProperty] instead.
|
|
||||||
*/
|
*/
|
||||||
override val config: Config by lazy {
|
override fun getOwnProperty(name: Name): MetaItem? {
|
||||||
properties ?: Config().also { config ->
|
return properties?.getItem(name)
|
||||||
properties = config.also {
|
}
|
||||||
it.onChange(this) { name, _, _ -> propertyChanged(name) }
|
|
||||||
}
|
override fun getProperty(
|
||||||
|
name: Name,
|
||||||
|
inherit: Boolean,
|
||||||
|
includeStyles: Boolean,
|
||||||
|
includeDefaults: Boolean,
|
||||||
|
): MetaItem? = sequence {
|
||||||
|
yield(getOwnProperty(name))
|
||||||
|
if (includeStyles) {
|
||||||
|
yieldAll(getStyleItems(name))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Transient
|
|
||||||
private val listeners = HashSet<PropertyListener>()
|
|
||||||
|
|
||||||
override fun propertyChanged(name: Name) {
|
|
||||||
if (name == STYLE_KEY) {
|
|
||||||
updateStyles(properties?.get(STYLE_KEY)?.stringList ?: emptyList())
|
|
||||||
}
|
|
||||||
for (listener in listeners) {
|
|
||||||
listener.action(name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPropertyChange(owner: Any?, action: (Name) -> Unit) {
|
|
||||||
listeners.add(PropertyListener(owner, action))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun removeChangeListener(owner: Any?) {
|
|
||||||
listeners.removeAll { owner == null || it.owner == owner }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All available properties in a layered form
|
|
||||||
*/
|
|
||||||
override val allProperties: Laminate
|
|
||||||
get() = Laminate(
|
|
||||||
properties,
|
|
||||||
allStyles,
|
|
||||||
parent?.allProperties,
|
|
||||||
descriptor?.defaultMeta(),
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? = sequence {
|
|
||||||
yield(properties?.get(name))
|
|
||||||
yieldAll(getStyleItems(name))
|
|
||||||
if (inherit) {
|
if (inherit) {
|
||||||
yield(parent?.getProperty(name, inherit))
|
yield(parent?.getProperty(name, inherit, includeStyles, includeDefaults))
|
||||||
}
|
}
|
||||||
yield(descriptor?.get(name)?.defaultItem())
|
yield(descriptor?.get(name)?.defaultItem())
|
||||||
}.merge()
|
}.merge()
|
||||||
|
|
||||||
/**
|
override fun setProperty(name: Name, item: MetaItem?, notify: Boolean) {
|
||||||
* Reset all properties to their default values
|
getOrCreateConfig().setItem(name, item)
|
||||||
*/
|
if (notify) {
|
||||||
public fun resetProperties() {
|
scope.launch {
|
||||||
properties?.removeListener(this)
|
notifyPropertyChanged(name)
|
||||||
properties = null
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val descriptor: NodeDescriptor? get() = null
|
||||||
|
|
||||||
|
private suspend fun updateStyles(names: List<String>) {
|
||||||
|
names.mapNotNull { getStyle(it) }.asSequence()
|
||||||
|
.flatMap { it.items.asSequence() }
|
||||||
|
.distinctBy { it.key }
|
||||||
|
.forEach {
|
||||||
|
notifyPropertyChanged(it.key.asName())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//TODO check memory consumption for the flow
|
||||||
|
@Transient
|
||||||
|
private val propertyInvalidationFlow: MutableSharedFlow<Name> = MutableSharedFlow()
|
||||||
|
|
||||||
|
@DFExperimental
|
||||||
|
override val propertyChanges: Flow<Name> get() = propertyInvalidationFlow
|
||||||
|
|
||||||
|
override fun onPropertyChange(scope: CoroutineScope, callback: suspend (Name) -> Unit) {
|
||||||
|
propertyInvalidationFlow.onEach(callback).launchIn(scope)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun notifyPropertyChanged(propertyName: Name) {
|
||||||
|
if (propertyName == STYLE_KEY) {
|
||||||
|
updateStyles(styles)
|
||||||
|
}
|
||||||
|
propertyInvalidationFlow.emit(propertyName)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun update(change: VisionChange) {
|
override fun update(change: VisionChange) {
|
||||||
change.propertyChange[Name.EMPTY]?.let {
|
change.properties?.let {
|
||||||
config.update(it)
|
updateProperties(Name.EMPTY, it.asMetaItem())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,6 +141,21 @@ public open class VisionBase : Vision {
|
|||||||
multiple = true
|
multiple = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public fun Vision.updateProperties(at: Name, item: MetaItem) {
|
||||||
|
when (item) {
|
||||||
|
is ValueItem -> {
|
||||||
|
if (item.value == Null) {
|
||||||
|
setProperty(at, null)
|
||||||
|
} else
|
||||||
|
setProperty(at, item)
|
||||||
|
}
|
||||||
|
is NodeItem -> item.node.items.forEach { (token, childItem) ->
|
||||||
|
updateProperties(at + token, childItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,10 +3,14 @@ package hep.dataforge.vision
|
|||||||
import hep.dataforge.meta.*
|
import hep.dataforge.meta.*
|
||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.Name
|
||||||
import hep.dataforge.names.plus
|
import hep.dataforge.names.plus
|
||||||
|
import hep.dataforge.values.Null
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.serialization.*
|
import kotlinx.serialization.*
|
||||||
|
import kotlin.jvm.Synchronized
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -14,29 +18,42 @@ import kotlin.time.Duration
|
|||||||
*/
|
*/
|
||||||
public class VisionChangeBuilder : VisionContainerBuilder<Vision> {
|
public class VisionChangeBuilder : VisionContainerBuilder<Vision> {
|
||||||
|
|
||||||
private val propertyChange = HashMap<Name, Config>()
|
private var reset: Boolean = false
|
||||||
private val childrenChange = HashMap<Name, Vision?>()
|
private var vision: Vision? = null
|
||||||
|
private val propertyChange = Config()
|
||||||
|
private val children: HashMap<Name, VisionChangeBuilder> = HashMap()
|
||||||
|
|
||||||
public fun isEmpty(): Boolean = propertyChange.isEmpty() && childrenChange.isEmpty()
|
public fun isEmpty(): Boolean = propertyChange.isEmpty() && propertyChange.isEmpty() && children.isEmpty()
|
||||||
|
|
||||||
public fun propertyChanged(visionName: Name, propertyName: Name, item: MetaItem<*>?) {
|
@Synchronized
|
||||||
propertyChange
|
private fun getOrPutChild(visionName: Name): VisionChangeBuilder =
|
||||||
.getOrPut(visionName) { Config() }
|
children.getOrPut(visionName) { VisionChangeBuilder() }
|
||||||
.setItem(propertyName, item)
|
|
||||||
|
public fun propertyChanged(visionName: Name, propertyName: Name, item: MetaItem?) {
|
||||||
|
if (visionName == Name.EMPTY) {
|
||||||
|
//Write property removal as [Null]
|
||||||
|
propertyChange[propertyName] = (item ?: Null.asMetaItem())
|
||||||
|
} else {
|
||||||
|
getOrPutChild(visionName).propertyChanged(Name.EMPTY, propertyName, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun set(name: Name, child: Vision?) {
|
override fun set(name: Name, child: Vision?) {
|
||||||
childrenChange[name] = child
|
getOrPutChild(name).apply {
|
||||||
|
vision = child
|
||||||
|
reset = vision == null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Isolate collected changes by creating detached copies of given visions
|
* Isolate collected changes by creating detached copies of given visions
|
||||||
*/
|
*/
|
||||||
public fun isolate(manager: VisionManager): VisionChange = VisionChange(
|
public fun isolate(manager: VisionManager): VisionChange = VisionChange(
|
||||||
propertyChange.mapValues { it.value.seal() },
|
reset,
|
||||||
childrenChange.mapValues { it.value?.isolate(manager) }
|
vision?.isolate(manager),
|
||||||
|
if (propertyChange.isEmpty()) null else propertyChange.seal(),
|
||||||
|
if (children.isEmpty()) null else children.mapValues { it.value.isolate(manager) }
|
||||||
)
|
)
|
||||||
//TODO optimize isolation for visions without parents?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Vision.isolate(manager: VisionManager): Vision {
|
private fun Vision.isolate(manager: VisionManager): Vision {
|
||||||
@ -47,16 +64,11 @@ private fun Vision.isolate(manager: VisionManager): Vision {
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
public data class VisionChange(
|
public data class VisionChange(
|
||||||
val propertyChange: Map<Name, @Serializable(MetaSerializer::class) Meta>,
|
public val reset: Boolean = false,
|
||||||
val childrenChange: Map<Name, Vision?>,
|
public val vision: Vision? = null,
|
||||||
) {
|
@Serializable(MetaSerializer::class) public val properties: Meta? = null,
|
||||||
public fun isEmpty(): Boolean = propertyChange.isEmpty() && childrenChange.isEmpty()
|
public val children: Map<Name, VisionChange>? = null,
|
||||||
|
)
|
||||||
/**
|
|
||||||
* A shortcut to the top level property dif
|
|
||||||
*/
|
|
||||||
public val properties: Meta? get() = propertyChange[Name.EMPTY]
|
|
||||||
}
|
|
||||||
|
|
||||||
public inline fun VisionChange(manager: VisionManager, block: VisionChangeBuilder.() -> Unit): VisionChange =
|
public inline fun VisionChange(manager: VisionManager, block: VisionChangeBuilder.() -> Unit): VisionChange =
|
||||||
VisionChangeBuilder().apply(block).isolate(manager)
|
VisionChangeBuilder().apply(block).isolate(manager)
|
||||||
@ -69,16 +81,9 @@ private fun CoroutineScope.collectChange(
|
|||||||
) {
|
) {
|
||||||
|
|
||||||
//Collect properties change
|
//Collect properties change
|
||||||
source.config.onChange(this) { propertyName, oldItem, newItem ->
|
source.onPropertyChange(this) { propertyName ->
|
||||||
if (oldItem != newItem) {
|
val newItem = source.getProperty(propertyName, inherit = false, includeStyles = false, includeDefaults = false)
|
||||||
launch {
|
collector().propertyChanged(name, propertyName, newItem)
|
||||||
collector().propertyChanged(name, propertyName, newItem)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
coroutineContext[Job]?.invokeOnCompletion {
|
|
||||||
source.config.removeListener(this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (source is VisionGroup) {
|
if (source is VisionGroup) {
|
||||||
@ -89,17 +94,12 @@ private fun CoroutineScope.collectChange(
|
|||||||
|
|
||||||
//Subscribe for structure change
|
//Subscribe for structure change
|
||||||
if (source is MutableVisionGroup) {
|
if (source is MutableVisionGroup) {
|
||||||
source.onStructureChange(this) { token, before, after ->
|
source.structureChanges.onEach { (token, _, after) ->
|
||||||
before?.removeChangeListener(this)
|
|
||||||
(before as? MutableVisionGroup)?.removeStructureChangeListener(this)
|
|
||||||
if (after != null) {
|
if (after != null) {
|
||||||
collectChange(name + token, after, collector)
|
collectChange(name + token, after, collector)
|
||||||
}
|
}
|
||||||
collector()[name + token] = after
|
collector()[name + token] = after
|
||||||
}
|
}.launchIn(this)
|
||||||
coroutineContext[Job]?.invokeOnCompletion {
|
|
||||||
source.removeStructureChangeListener(this)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -111,17 +111,23 @@ public fun Vision.flowChanges(
|
|||||||
): Flow<VisionChange> = flow {
|
): Flow<VisionChange> = flow {
|
||||||
|
|
||||||
var collector = VisionChangeBuilder()
|
var collector = VisionChangeBuilder()
|
||||||
manager.context.collectChange(Name.EMPTY, this@flowChanges) { collector }
|
coroutineScope {
|
||||||
|
collectChange(Name.EMPTY, this@flowChanges) { collector }
|
||||||
|
|
||||||
while (currentCoroutineContext().isActive) {
|
//Send initial vision state
|
||||||
//Wait for changes to accumulate
|
val initialChange = VisionChange(vision = isolate(manager))
|
||||||
delay(collectionDuration)
|
emit(initialChange)
|
||||||
//Propagate updates only if something is changed
|
|
||||||
if (!collector.isEmpty()) {
|
while (currentCoroutineContext().isActive) {
|
||||||
//emit changes
|
//Wait for changes to accumulate
|
||||||
emit(collector.isolate(manager))
|
delay(collectionDuration)
|
||||||
//Reset the collector
|
//Propagate updates only if something is changed
|
||||||
collector = VisionChangeBuilder()
|
if (!collector.isEmpty()) {
|
||||||
|
//emit changes
|
||||||
|
emit(collector.isolate(manager))
|
||||||
|
//Reset the collector
|
||||||
|
collector = VisionChangeBuilder()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,6 +2,7 @@ package hep.dataforge.vision
|
|||||||
|
|
||||||
import hep.dataforge.names.*
|
import hep.dataforge.names.*
|
||||||
import hep.dataforge.provider.Provider
|
import hep.dataforge.provider.Provider
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
public interface VisionContainer<out V : Vision> {
|
public interface VisionContainer<out V : Vision> {
|
||||||
public operator fun get(name: Name): V?
|
public operator fun get(name: Name): V?
|
||||||
@ -44,16 +45,6 @@ public interface VisionGroup : Provider, Vision, VisionContainer<Vision> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A fix for serialization bug that writes all proper parents inside the tree after deserialization
|
|
||||||
*/
|
|
||||||
public fun attachChildren() {
|
|
||||||
children.values.forEach {
|
|
||||||
it.parent = this
|
|
||||||
(it as? VisionGroup)?.attachChildren()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public companion object {
|
public companion object {
|
||||||
public const val STYLE_TARGET: String = "style"
|
public const val STYLE_TARGET: String = "style"
|
||||||
}
|
}
|
||||||
@ -75,19 +66,12 @@ public interface VisionContainerBuilder<in V : Vision> {
|
|||||||
*/
|
*/
|
||||||
public interface MutableVisionGroup : VisionGroup, VisionContainerBuilder<Vision> {
|
public interface MutableVisionGroup : VisionGroup, VisionContainerBuilder<Vision> {
|
||||||
|
|
||||||
/**
|
public data class StructureChange(val token: NameToken, val before: Vision?, val after: Vision?)
|
||||||
* Add listener for children structure change.
|
|
||||||
* @param owner the handler to properly remove listeners
|
|
||||||
* @param action First argument of the action is the name of changed child. Second argument is the new value of the object.
|
|
||||||
*/
|
|
||||||
public fun onStructureChange(owner: Any?, action: (token: NameToken, before: Vision?, after: Vision?) -> Unit)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove children change listener
|
* Flow structure changes of this group. Unconsumed changes are discarded
|
||||||
*/
|
*/
|
||||||
public fun removeStructureChangeListener(owner: Any?)
|
public val structureChanges: Flow<StructureChange>
|
||||||
|
|
||||||
// public operator fun set(name: Name, child: Vision?)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public operator fun <V : Vision> VisionContainer<V>.get(str: String): V? = get(str.toName())
|
public operator fun <V : Vision> VisionContainer<V>.get(str: String): V? = get(str.toName())
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package hep.dataforge.vision
|
package hep.dataforge.vision
|
||||||
|
|
||||||
import hep.dataforge.meta.configure
|
|
||||||
import hep.dataforge.names.*
|
import hep.dataforge.names.*
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.Transient
|
import kotlinx.serialization.Transient
|
||||||
@ -9,71 +11,45 @@ import kotlinx.serialization.Transient
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract implementation of mutable group of [Vision]
|
* Abstract implementation of mutable group of [Vision]
|
||||||
|
*
|
||||||
|
* @param childrenInternal Internal mutable container for group children
|
||||||
*/
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
@SerialName("vision.group")
|
@SerialName("vision.group")
|
||||||
public open class VisionGroupBase : VisionBase(), MutableVisionGroup {
|
public open class VisionGroupBase(
|
||||||
|
@SerialName("children") internal val childrenInternal: MutableMap<NameToken, Vision> = LinkedHashMap(),
|
||||||
/**
|
) : VisionBase(), MutableVisionGroup {
|
||||||
* Internal mutable container for group children
|
|
||||||
* TODO made protected due to [https://github.com/Kotlin/kotlinx.serialization/issues/1200]
|
|
||||||
*/
|
|
||||||
@SerialName("children")
|
|
||||||
protected val childrenInternal: MutableMap<NameToken, Vision> = LinkedHashMap()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A map of top level named children
|
* A map of top level named children
|
||||||
*/
|
*/
|
||||||
override val children: Map<NameToken, Vision> get() = childrenInternal
|
override val children: Map<NameToken, Vision> get() = childrenInternal
|
||||||
|
|
||||||
override fun propertyChanged(name: Name) {
|
init {
|
||||||
super.propertyChanged(name)
|
childrenInternal.values.forEach {
|
||||||
for (obj in this) {
|
it.parent = this
|
||||||
obj.propertyChanged(name)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private data class StructureChangeListener(
|
override suspend fun notifyPropertyChanged(propertyName: Name) {
|
||||||
val owner: Any?,
|
super.notifyPropertyChanged(propertyName)
|
||||||
val callback: (token: NameToken, before: Vision?, after: Vision?) -> Unit,
|
for (obj in this) {
|
||||||
)
|
obj.notifyPropertyChanged(propertyName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Transient
|
@Transient
|
||||||
private val structureChangeListeners = HashSet<StructureChangeListener>()
|
private val _structureChanges: MutableSharedFlow<MutableVisionGroup.StructureChange> = MutableSharedFlow()
|
||||||
|
|
||||||
/**
|
override val structureChanges: SharedFlow<MutableVisionGroup.StructureChange> get() = _structureChanges
|
||||||
* Add listener for children change
|
|
||||||
*/
|
|
||||||
override fun onStructureChange(owner: Any?, action: (token: NameToken, before: Vision?, after: Vision?) -> Unit) {
|
|
||||||
structureChangeListeners.add(
|
|
||||||
StructureChangeListener(
|
|
||||||
owner,
|
|
||||||
action
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove children change listener
|
|
||||||
*/
|
|
||||||
override fun removeStructureChangeListener(owner: Any?) {
|
|
||||||
structureChangeListeners.removeAll { owner == null || it.owner === owner }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Propagate children change event upwards
|
* Propagate children change event upwards
|
||||||
*/
|
*/
|
||||||
protected fun childrenChanged(name: NameToken, before: Vision?, after: Vision?) {
|
private fun childrenChanged(name: NameToken, before: Vision?, after: Vision?) {
|
||||||
structureChangeListeners.forEach { it.callback(name, before, after) }
|
scope.launch {
|
||||||
}
|
_structureChanges.emit(MutableVisionGroup.StructureChange(name, before, after))
|
||||||
|
}
|
||||||
/**
|
|
||||||
* Remove a child with given name token
|
|
||||||
*/
|
|
||||||
public fun removeChild(token: NameToken): Vision? {
|
|
||||||
val removed = childrenInternal.remove(token)
|
|
||||||
removed?.parent = null
|
|
||||||
return removed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -91,12 +67,22 @@ public open class VisionGroupBase : VisionBase(), MutableVisionGroup {
|
|||||||
/**
|
/**
|
||||||
* Set parent for given child and attach it
|
* Set parent for given child and attach it
|
||||||
*/
|
*/
|
||||||
private fun attachChild(token: NameToken, child: Vision) {
|
private fun attachChild(token: NameToken, child: Vision?) {
|
||||||
if (child.parent == null) {
|
val before = children[token]
|
||||||
child.parent = this
|
when {
|
||||||
childrenInternal[token] = child
|
child == null -> {
|
||||||
} else if (child.parent !== this) {
|
childrenInternal.remove(token)
|
||||||
error("Can't reassign existing parent for $child")
|
}
|
||||||
|
child.parent == null -> {
|
||||||
|
child.parent = this
|
||||||
|
childrenInternal[token] = child
|
||||||
|
}
|
||||||
|
child.parent !== this -> {
|
||||||
|
error("Can't reassign existing parent for $child")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (before != child) {
|
||||||
|
childrenChanged(token, before, child)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,13 +119,7 @@ public open class VisionGroupBase : VisionBase(), MutableVisionGroup {
|
|||||||
}
|
}
|
||||||
name.length == 1 -> {
|
name.length == 1 -> {
|
||||||
val token = name.tokens.first()
|
val token = name.tokens.first()
|
||||||
val before = children[token]
|
attachChild(token, child)
|
||||||
if (child == null) {
|
|
||||||
removeChild(token)
|
|
||||||
} else {
|
|
||||||
attachChild(token, child)
|
|
||||||
}
|
|
||||||
childrenChanged(token, before, child)
|
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
//TODO add safety check
|
//TODO add safety check
|
||||||
@ -150,18 +130,12 @@ public open class VisionGroupBase : VisionBase(), MutableVisionGroup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun update(change: VisionChange) {
|
override fun update(change: VisionChange) {
|
||||||
//update stylesheet
|
change.children?.forEach { (name, change) ->
|
||||||
// val changeStyleSheet = change.styleSheet
|
when {
|
||||||
// if (changeStyleSheet != null) {
|
change.reset -> set(name, null)
|
||||||
// styleSheet {
|
change.vision != null -> set(name, change.vision)
|
||||||
// update(changeStyleSheet)
|
else -> get(name)?.update(change)
|
||||||
// }
|
}
|
||||||
// }
|
|
||||||
change.propertyChange.forEach {(childName,configChange)->
|
|
||||||
get(childName)?.configure(configChange)
|
|
||||||
}
|
|
||||||
change.childrenChange.forEach { (name, child) ->
|
|
||||||
set(name, child)
|
|
||||||
}
|
}
|
||||||
super.update(change)
|
super.update(change)
|
||||||
}
|
}
|
||||||
|
@ -30,16 +30,11 @@ public class VisionManager(meta: Meta) : AbstractPlugin(meta) {
|
|||||||
serializersModule = this@VisionManager.serializersModule
|
serializersModule = this@VisionManager.serializersModule
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun decodeFromString(string: String): Vision = jsonFormat.decodeFromString(visionSerializer, string).also {
|
public fun decodeFromString(string: String): Vision = jsonFormat.decodeFromString(visionSerializer, string)
|
||||||
(it as? VisionGroup)?.attachChildren()
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun encodeToString(vision: Vision): String = jsonFormat.encodeToString(visionSerializer, vision)
|
public fun encodeToString(vision: Vision): String = jsonFormat.encodeToString(visionSerializer, vision)
|
||||||
|
|
||||||
public fun decodeFromJson(json: JsonElement): Vision =
|
public fun decodeFromJson(json: JsonElement): Vision = jsonFormat.decodeFromJsonElement(visionSerializer, json)
|
||||||
jsonFormat.decodeFromJsonElement(visionSerializer, json).also {
|
|
||||||
(it as? VisionGroup)?.attachChildren()
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun encodeToJsonElement(vision: Vision): JsonElement =
|
public fun encodeToJsonElement(vision: Vision): JsonElement =
|
||||||
jsonFormat.encodeToJsonElement(visionSerializer, vision)
|
jsonFormat.encodeToJsonElement(visionSerializer, vision)
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
package hep.dataforge.vision
|
||||||
|
|
||||||
|
import hep.dataforge.meta.MetaItem
|
||||||
|
import hep.dataforge.names.Name
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Property containers are used to create a symmetric behaviors for vision properties and style builders
|
||||||
|
*/
|
||||||
|
public interface VisionPropertyContainer<out T> {
|
||||||
|
public fun getProperty(
|
||||||
|
name: Name,
|
||||||
|
inherit: Boolean = false,
|
||||||
|
includeStyles: Boolean = true,
|
||||||
|
includeDefaults: Boolean = true,
|
||||||
|
): MetaItem?
|
||||||
|
|
||||||
|
public fun setProperty(name: Name, item: MetaItem?, notify: Boolean = true)
|
||||||
|
}
|
@ -73,7 +73,7 @@ public abstract class VisionTagConsumer<R>(
|
|||||||
|
|
||||||
@OptIn(DFExperimental::class)
|
@OptIn(DFExperimental::class)
|
||||||
public inline fun <T> TagConsumer<T>.vision(
|
public inline fun <T> TagConsumer<T>.vision(
|
||||||
name: String,
|
name: String = DEFAULT_VISION_NAME,
|
||||||
visionProvider: VisionOutput.() -> Vision,
|
visionProvider: VisionOutput.() -> Vision,
|
||||||
): T = vision(name.toName(), visionProvider)
|
): T = vision(name.toName(), visionProvider)
|
||||||
|
|
||||||
@ -103,5 +103,7 @@ public abstract class VisionTagConsumer<R>(
|
|||||||
public const val OUTPUT_NAME_ATTRIBUTE: String = "data-output-name"
|
public const val OUTPUT_NAME_ATTRIBUTE: String = "data-output-name"
|
||||||
public const val OUTPUT_ENDPOINT_ATTRIBUTE: String = "data-output-endpoint"
|
public const val OUTPUT_ENDPOINT_ATTRIBUTE: String = "data-output-endpoint"
|
||||||
public const val DEFAULT_ENDPOINT: String = "."
|
public const val DEFAULT_ENDPOINT: String = "."
|
||||||
|
|
||||||
|
public const val DEFAULT_VISION_NAME = "vision"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -18,9 +18,10 @@ public fun FlowContent.embedVisionFragment(
|
|||||||
val consumer = object : VisionTagConsumer<Any?>(consumer, idPrefix) {
|
val consumer = object : VisionTagConsumer<Any?>(consumer, idPrefix) {
|
||||||
override fun DIV.renderVision(name: Name, vision: Vision, outputMeta: Meta) {
|
override fun DIV.renderVision(name: Name, vision: Vision, outputMeta: Meta) {
|
||||||
script {
|
script {
|
||||||
|
type = "text/json"
|
||||||
attributes["class"] = OUTPUT_DATA_CLASS
|
attributes["class"] = OUTPUT_DATA_CLASS
|
||||||
unsafe {
|
unsafe {
|
||||||
+manager.encodeToString(vision)
|
+"\n${manager.encodeToString(vision)}\n"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,33 +1,28 @@
|
|||||||
package hep.dataforge.vision
|
package hep.dataforge.vision
|
||||||
|
|
||||||
import hep.dataforge.meta.Laminate
|
import hep.dataforge.meta.*
|
||||||
import hep.dataforge.meta.MetaItem
|
|
||||||
import hep.dataforge.meta.descriptors.NodeDescriptor
|
|
||||||
import hep.dataforge.meta.node
|
|
||||||
import hep.dataforge.names.Name
|
|
||||||
import hep.dataforge.values.ValueType
|
|
||||||
import hep.dataforge.values.asValue
|
import hep.dataforge.values.asValue
|
||||||
|
|
||||||
@DslMarker
|
@DslMarker
|
||||||
public annotation class VisionBuilder
|
public annotation class VisionBuilder
|
||||||
|
|
||||||
|
public fun Sequence<MetaItem?>.merge(): MetaItem? = when (val first = firstOrNull { it != null }) {
|
||||||
public fun Sequence<MetaItem<*>?>.merge(): MetaItem<*>? {
|
null -> null
|
||||||
return when (val first = firstOrNull { it != null }) {
|
is ValueItem -> first //fast search for first entry if it is value
|
||||||
null -> null
|
is NodeItem -> {
|
||||||
is MetaItem.ValueItem -> first //fast search for first entry if it is value
|
//merge nodes if first encountered node is meta
|
||||||
is MetaItem.NodeItem -> {
|
val laminate: Laminate = Laminate(mapNotNull { it.node }.toList())
|
||||||
//merge nodes if first encountered node is meta
|
NodeItem(laminate)
|
||||||
val laminate: Laminate = Laminate(mapNotNull { it.node }.toList())
|
|
||||||
MetaItem.NodeItem(laminate)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public inline fun <reified E : Enum<E>> NodeDescriptor.enum(key: Name, default: E?): Unit = value(key) {
|
/**
|
||||||
type(ValueType.STRING)
|
* Control visibility of the element
|
||||||
default?.let {
|
*/
|
||||||
default(default)
|
public var Vision.visible: Boolean?
|
||||||
}
|
get() = getProperty(Vision.VISIBLE_KEY).boolean
|
||||||
allowedValues = enumValues<E>().map { it.asValue() }
|
set(value) = setProperty(Vision.VISIBLE_KEY, value?.asValue())
|
||||||
}
|
|
||||||
|
public fun Vision.configure(meta: Meta?): Unit = update(VisionChange(properties = meta))
|
||||||
|
|
||||||
|
public fun Vision.configure(block: MetaBuilder.() -> Unit): Unit = configure(Meta(block))
|
@ -1,27 +0,0 @@
|
|||||||
package hep.dataforge.vision
|
|
||||||
|
|
||||||
import hep.dataforge.meta.*
|
|
||||||
import hep.dataforge.meta.descriptors.ValueDescriptor
|
|
||||||
import hep.dataforge.meta.descriptors.attributes
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extension property to access the "widget" key of [ValueDescriptor]
|
|
||||||
*/
|
|
||||||
public var ValueDescriptor.widget: Meta
|
|
||||||
get() = attributes["widget"].node ?: Meta.EMPTY
|
|
||||||
set(value) {
|
|
||||||
attributes {
|
|
||||||
set("widget", value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extension property to access the "widget.type" key of [ValueDescriptor]
|
|
||||||
*/
|
|
||||||
public var ValueDescriptor.widgetType: String?
|
|
||||||
get() = attributes["widget.type"].string
|
|
||||||
set(value) {
|
|
||||||
attributes{
|
|
||||||
set("widget.type", value)
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,79 @@
|
|||||||
|
package hep.dataforge.vision
|
||||||
|
|
||||||
|
import hep.dataforge.meta.*
|
||||||
|
import hep.dataforge.meta.descriptors.ItemDescriptor
|
||||||
|
import hep.dataforge.meta.descriptors.NodeDescriptor
|
||||||
|
import hep.dataforge.meta.descriptors.ValueDescriptor
|
||||||
|
import hep.dataforge.meta.descriptors.attributes
|
||||||
|
import hep.dataforge.names.Name
|
||||||
|
import hep.dataforge.values.ValueType
|
||||||
|
import hep.dataforge.values.asValue
|
||||||
|
|
||||||
|
private const val INHERITED_DESCRIPTOR_ATTRIBUTE = "inherited"
|
||||||
|
private const val STYLE_DESCRIPTOR_ATTRIBUTE = "useStyles"
|
||||||
|
|
||||||
|
public var ItemDescriptor.inherited: Boolean
|
||||||
|
get() = attributes[INHERITED_DESCRIPTOR_ATTRIBUTE].boolean ?: false
|
||||||
|
set(value) = attributes {
|
||||||
|
set(INHERITED_DESCRIPTOR_ATTRIBUTE, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
public var ItemDescriptor.usesStyles: Boolean
|
||||||
|
get() = attributes[STYLE_DESCRIPTOR_ATTRIBUTE].boolean ?: true
|
||||||
|
set(value) = attributes {
|
||||||
|
set(STYLE_DESCRIPTOR_ATTRIBUTE, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public val Vision.describedProperties: Meta
|
||||||
|
get() = Meta {
|
||||||
|
descriptor?.items?.forEach { (key, descriptor) ->
|
||||||
|
key put getProperty(key, inherit = descriptor.inherited)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension property to access the "widget" key of [ValueDescriptor]
|
||||||
|
*/
|
||||||
|
public var ValueDescriptor.widget: Meta
|
||||||
|
get() = attributes["widget"].node ?: Meta.EMPTY
|
||||||
|
set(value) {
|
||||||
|
attributes {
|
||||||
|
set("widget", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension property to access the "widget.type" key of [ValueDescriptor]
|
||||||
|
*/
|
||||||
|
public var ValueDescriptor.widgetType: String?
|
||||||
|
get() = attributes["widget.type"].string
|
||||||
|
set(value) {
|
||||||
|
attributes {
|
||||||
|
set("widget.type", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true, this item is hidden in property editor. Default is false
|
||||||
|
*/
|
||||||
|
public val ItemDescriptor.hidden: Boolean
|
||||||
|
get() = attributes["widget.hide"].boolean ?: false
|
||||||
|
|
||||||
|
public fun ItemDescriptor.hide(): Unit = attributes {
|
||||||
|
set("widget.hide", true)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public inline fun <reified E : Enum<E>> NodeDescriptor.enum(
|
||||||
|
key: Name,
|
||||||
|
default: E?,
|
||||||
|
crossinline modifier: ValueDescriptor.() -> Unit = {},
|
||||||
|
): Unit = value(key) {
|
||||||
|
type(ValueType.STRING)
|
||||||
|
default?.let {
|
||||||
|
default(default)
|
||||||
|
}
|
||||||
|
allowedValues = enumValues<E>().map { it.asValue() }
|
||||||
|
modifier()
|
||||||
|
}
|
@ -1,9 +1,9 @@
|
|||||||
package hep.dataforge.vision.html
|
package hep.dataforge.vision.html
|
||||||
|
|
||||||
import hep.dataforge.meta.DFExperimental
|
import hep.dataforge.meta.DFExperimental
|
||||||
import hep.dataforge.meta.configure
|
|
||||||
import hep.dataforge.meta.set
|
import hep.dataforge.meta.set
|
||||||
import hep.dataforge.vision.VisionBase
|
import hep.dataforge.vision.VisionBase
|
||||||
|
import hep.dataforge.vision.configure
|
||||||
import kotlinx.html.*
|
import kotlinx.html.*
|
||||||
import kotlinx.html.stream.createHTML
|
import kotlinx.html.stream.createHTML
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
@ -35,7 +35,7 @@ class HtmlTagTest {
|
|||||||
div {
|
div {
|
||||||
h2 { +"Properties" }
|
h2 { +"Properties" }
|
||||||
ul {
|
ul {
|
||||||
vision.properties?.items?.forEach {
|
(vision as? VisionBase)?.meta?.items?.forEach {
|
||||||
li {
|
li {
|
||||||
a { +it.key.toString() }
|
a { +it.key.toString() }
|
||||||
p { +it.value.toString() }
|
p { +it.value.toString() }
|
||||||
|
@ -18,6 +18,7 @@ import org.w3c.dom.WebSocket
|
|||||||
import org.w3c.dom.asList
|
import org.w3c.dom.asList
|
||||||
import org.w3c.dom.get
|
import org.w3c.dom.get
|
||||||
import org.w3c.dom.url.URL
|
import org.w3c.dom.url.URL
|
||||||
|
import kotlin.collections.set
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
public class VisionClient : AbstractPlugin() {
|
public class VisionClient : AbstractPlugin() {
|
||||||
@ -80,14 +81,18 @@ public class VisionClient : AbstractPlugin() {
|
|||||||
renderVision(element, embeddedVision, outputMeta)
|
renderVision(element, embeddedVision, outputMeta)
|
||||||
}
|
}
|
||||||
|
|
||||||
val endpoint = resolveEndpoint(element)
|
element.attributes[OUTPUT_FETCH_ATTRIBUTE]?.let { attr ->
|
||||||
logger.info { "Vision server is resolved to $endpoint" }
|
|
||||||
|
|
||||||
element.attributes[OUTPUT_FETCH_ATTRIBUTE]?.let {
|
val fetchUrl = if (attr.value.isBlank() || attr.value == "auto") {
|
||||||
|
val endpoint = resolveEndpoint(element)
|
||||||
val fetchUrl = URL(endpoint).apply {
|
logger.info { "Vision server is resolved to $endpoint" }
|
||||||
|
URL(endpoint).apply {
|
||||||
|
pathname += "/vision"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
URL(attr.value)
|
||||||
|
}.apply {
|
||||||
searchParams.append("name", name)
|
searchParams.append("name", name)
|
||||||
pathname += "/vision"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info { "Fetching vision data from $fetchUrl" }
|
logger.info { "Fetching vision data from $fetchUrl" }
|
||||||
@ -98,16 +103,22 @@ public class VisionClient : AbstractPlugin() {
|
|||||||
renderVision(element, vision, outputMeta)
|
renderVision(element, vision, outputMeta)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.error { "Failed to fetch initial vision state from $endpoint" }
|
logger.error { "Failed to fetch initial vision state from $fetchUrl" }
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
element.attributes[OUTPUT_CONNECT_ATTRIBUTE]?.let {
|
element.attributes[OUTPUT_CONNECT_ATTRIBUTE]?.let { attr ->
|
||||||
|
val wsUrl = if (attr.value.isBlank() || attr.value == "auto") {
|
||||||
val wsUrl = URL(endpoint).apply {
|
val endpoint = resolveEndpoint(element)
|
||||||
pathname += "/ws"
|
logger.info { "Vision server is resolved to $endpoint" }
|
||||||
|
URL(endpoint).apply {
|
||||||
|
pathname += "/ws"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
URL(attr.value)
|
||||||
|
}.apply {
|
||||||
protocol = "ws"
|
protocol = "ws"
|
||||||
searchParams.append("name", name)
|
searchParams.append("name", name)
|
||||||
}
|
}
|
||||||
@ -118,13 +129,18 @@ public class VisionClient : AbstractPlugin() {
|
|||||||
onmessage = { messageEvent ->
|
onmessage = { messageEvent ->
|
||||||
val stringData: String? = messageEvent.data as? String
|
val stringData: String? = messageEvent.data as? String
|
||||||
if (stringData != null) {
|
if (stringData != null) {
|
||||||
val dif = visionManager.jsonFormat.decodeFromString(
|
val change = visionManager.jsonFormat.decodeFromString(
|
||||||
VisionChange.serializer(),
|
VisionChange.serializer(),
|
||||||
stringData
|
stringData
|
||||||
)
|
)
|
||||||
logger.debug { "Got update $dif for output with name $name" }
|
|
||||||
visionMap[element]?.update(dif)
|
if (change.vision != null) {
|
||||||
?: logger.info { "Target vision for element $element with name $name not found" }
|
renderVision(element, change.vision, outputMeta)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug { "Got update $change for output with name $name" }
|
||||||
|
visionMap[element]?.update(change)
|
||||||
|
?: console.info("Target vision for element $element with name $name not found")
|
||||||
} else {
|
} else {
|
||||||
console.error("WebSocket message data is not a string")
|
console.error("WebSocket message data is not a string")
|
||||||
}
|
}
|
||||||
|
@ -35,30 +35,30 @@ public enum class ResourceLocation {
|
|||||||
EMBED
|
EMBED
|
||||||
}
|
}
|
||||||
|
|
||||||
internal const val DATAFORGE_ASSETS_PATH = ".dataforge/assets"
|
internal const val VISIONFORGE_ASSETS_PATH = ".dataforge/vision/assets"
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the asset exists in given local location and put it there if it does not
|
* Check if the asset exists in given local location and put it there if it does not
|
||||||
* @param
|
* @param
|
||||||
*/
|
*/
|
||||||
internal fun checkOrStoreFile(basePath: Path, filePath: Path, resource: String): Path {
|
internal fun checkOrStoreFile(htmlPath: Path, filePath: Path, resource: String): Path {
|
||||||
val fullPath = basePath.resolveSibling(filePath).toAbsolutePath()
|
val fullPath = htmlPath.resolveSibling(filePath).toAbsolutePath().resolve(resource)
|
||||||
|
|
||||||
if (Files.exists(fullPath)) {
|
if (Files.exists(fullPath)) {
|
||||||
//TODO checksum
|
//TODO checksum
|
||||||
} else {
|
} else {
|
||||||
//TODO add logging
|
//TODO add logging
|
||||||
|
|
||||||
val bytes = VisionManager::class.java.getResourceAsStream(resource).readAllBytes()
|
val bytes = VisionManager::class.java.getResourceAsStream("/$resource").readAllBytes()
|
||||||
Files.createDirectories(fullPath.parent)
|
Files.createDirectories(fullPath.parent)
|
||||||
Files.write(fullPath, bytes, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)
|
Files.write(fullPath, bytes, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)
|
||||||
}
|
}
|
||||||
|
|
||||||
return if (basePath.isAbsolute && fullPath.startsWith(basePath)) {
|
return if (htmlPath.isAbsolute && fullPath.startsWith(htmlPath.parent)) {
|
||||||
basePath.relativize(fullPath)
|
htmlPath.parent.relativize(fullPath)
|
||||||
} else {
|
} else {
|
||||||
filePath
|
fullPath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,7 +78,7 @@ internal fun embedScriptHeader(resource: String): HtmlFragment = {
|
|||||||
script {
|
script {
|
||||||
type = "text/javascript"
|
type = "text/javascript"
|
||||||
unsafe {
|
unsafe {
|
||||||
val bytes = VisionManager::class.java.getResourceAsStream(resource).readAllBytes()
|
val bytes = VisionManager::class.java.getResourceAsStream("/$resource").readAllBytes()
|
||||||
+bytes.toString(Charsets.UTF_8)
|
+bytes.toString(Charsets.UTF_8)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -100,20 +100,20 @@ internal fun fileCssHeader(
|
|||||||
* Make a script header, automatically copying file to appropriate location
|
* Make a script header, automatically copying file to appropriate location
|
||||||
*/
|
*/
|
||||||
@DFExperimental
|
@DFExperimental
|
||||||
public fun Context.Companion.scriptHeader(
|
public fun Context.scriptHeader(
|
||||||
scriptResource: String,
|
scriptResource: String,
|
||||||
basePath: Path,
|
htmlPath: Path,
|
||||||
resourceLocation: ResourceLocation,
|
resourceLocation: ResourceLocation,
|
||||||
): HtmlFragment {
|
): HtmlFragment {
|
||||||
val targetPath = when (resourceLocation) {
|
val targetPath = when (resourceLocation) {
|
||||||
ResourceLocation.LOCAL -> checkOrStoreFile(
|
ResourceLocation.LOCAL -> checkOrStoreFile(
|
||||||
basePath,
|
htmlPath,
|
||||||
Path.of(DATAFORGE_ASSETS_PATH),
|
Path.of(VISIONFORGE_ASSETS_PATH),
|
||||||
scriptResource
|
scriptResource
|
||||||
)
|
)
|
||||||
ResourceLocation.SYSTEM -> checkOrStoreFile(
|
ResourceLocation.SYSTEM -> checkOrStoreFile(
|
||||||
Path.of("."),
|
Path.of("."),
|
||||||
Path.of(System.getProperty("user.home")).resolve(DATAFORGE_ASSETS_PATH),
|
Path.of(System.getProperty("user.home")).resolve(VISIONFORGE_ASSETS_PATH),
|
||||||
scriptResource
|
scriptResource
|
||||||
)
|
)
|
||||||
ResourceLocation.EMBED -> null
|
ResourceLocation.EMBED -> null
|
||||||
|
@ -1,9 +1,16 @@
|
|||||||
package hep.dataforge.vision
|
package hep.dataforge.vision
|
||||||
|
|
||||||
|
import hep.dataforge.context.Context
|
||||||
import hep.dataforge.meta.DFExperimental
|
import hep.dataforge.meta.DFExperimental
|
||||||
import hep.dataforge.vision.html.*
|
import hep.dataforge.vision.html.HtmlFragment
|
||||||
import kotlinx.html.*
|
import hep.dataforge.vision.html.HtmlVisionFragment
|
||||||
|
import hep.dataforge.vision.html.embedVisionFragment
|
||||||
|
import hep.dataforge.vision.html.fragment
|
||||||
|
import kotlinx.html.body
|
||||||
|
import kotlinx.html.head
|
||||||
|
import kotlinx.html.meta
|
||||||
import kotlinx.html.stream.createHTML
|
import kotlinx.html.stream.createHTML
|
||||||
|
import kotlinx.html.title
|
||||||
import java.awt.Desktop
|
import java.awt.Desktop
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
@ -13,27 +20,27 @@ import java.nio.file.Path
|
|||||||
* Make a file with the embedded vision data
|
* Make a file with the embedded vision data
|
||||||
*/
|
*/
|
||||||
@DFExperimental
|
@DFExperimental
|
||||||
public fun HtmlVisionFragment.makeFile(
|
public fun Context.makeVisionFile(
|
||||||
manager: VisionManager,
|
fragment: HtmlVisionFragment,
|
||||||
vararg headers: HtmlFragment,
|
|
||||||
path: Path? = null,
|
path: Path? = null,
|
||||||
title: String = "VisionForge page",
|
title: String = "VisionForge page",
|
||||||
show: Boolean = true,
|
show: Boolean = true,
|
||||||
|
headerBuilder: (Path) -> HtmlFragment,
|
||||||
) {
|
) {
|
||||||
val actualFile = path ?: Files.createTempFile("tempPlot", ".html")
|
val actualFile = path?.let {
|
||||||
Files.createDirectories(actualFile.parent)
|
Path.of(System.getProperty("user.home")).resolve(path)
|
||||||
|
} ?: Files.createTempFile("tempPlot", ".html")
|
||||||
|
//Files.createDirectories(actualFile.parent)
|
||||||
val htmlString = createHTML().apply {
|
val htmlString = createHTML().apply {
|
||||||
head {
|
head {
|
||||||
meta {
|
meta {
|
||||||
charset = "utf-8"
|
charset = "utf-8"
|
||||||
headers.forEach {
|
fragment(headerBuilder(actualFile))
|
||||||
fragment(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
title(title)
|
title(title)
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
embedVisionFragment(manager, fragment = this@makeFile)
|
embedVisionFragment(visionManager, fragment = fragment)
|
||||||
}
|
}
|
||||||
}.finalize()
|
}.finalize()
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ import tornadofx.*
|
|||||||
/**
|
/**
|
||||||
* A display for meta and descriptor
|
* A display for meta and descriptor
|
||||||
*/
|
*/
|
||||||
sealed class FXMeta<M : MetaNode<M>> : Comparable<FXMeta<*>> {
|
sealed class FXMeta<M : TypedMeta<M>> : Comparable<FXMeta<*>> {
|
||||||
abstract val name: NameToken
|
abstract val name: NameToken
|
||||||
abstract val parent: FXMetaNode<M>?
|
abstract val parent: FXMetaNode<M>?
|
||||||
abstract val descriptionProperty: ObservableStringValue
|
abstract val descriptionProperty: ObservableStringValue
|
||||||
@ -35,7 +35,7 @@ sealed class FXMeta<M : MetaNode<M>> : Comparable<FXMeta<*>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun <M : MetaNode<M>> root(
|
fun <M : TypedMeta<M>> root(
|
||||||
node: M,
|
node: M,
|
||||||
descriptor: NodeDescriptor? = null,
|
descriptor: NodeDescriptor? = null,
|
||||||
rootName: String = "root"
|
rootName: String = "root"
|
||||||
@ -47,7 +47,7 @@ sealed class FXMeta<M : MetaNode<M>> : Comparable<FXMeta<*>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FXMetaNode<M : MetaNode<M>>(
|
class FXMetaNode<M : TypedMeta<M>>(
|
||||||
override val name: NameToken,
|
override val name: NameToken,
|
||||||
override val parent: FXMetaNode<M>?,
|
override val parent: FXMetaNode<M>?,
|
||||||
nodeValue: M? = null,
|
nodeValue: M? = null,
|
||||||
@ -89,7 +89,7 @@ class FXMetaNode<M : MetaNode<M>>(
|
|||||||
init {
|
init {
|
||||||
bind(nodeProperty, descriptorProperty)
|
bind(nodeProperty, descriptorProperty)
|
||||||
|
|
||||||
val listener: (Name, MetaItem<*>?, MetaItem<*>?) -> Unit = { name, _, _ ->
|
val listener: (Name, MetaItem?, MetaItem?) -> Unit = { name, _, _ ->
|
||||||
if (name.length == 1) invalidate()
|
if (name.length == 1) invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,7 +115,7 @@ class FXMetaNode<M : MetaNode<M>>(
|
|||||||
val actualItem = node?.items?.get(token)
|
val actualItem = node?.items?.get(token)
|
||||||
val actualDescriptor = descriptor?.items?.get(token.body)
|
val actualDescriptor = descriptor?.items?.get(token.body)
|
||||||
|
|
||||||
if (actualItem is MetaItem.NodeItem || actualDescriptor is NodeDescriptor) {
|
if (actualItem is NodeItem || actualDescriptor is NodeDescriptor) {
|
||||||
FXMetaNode(token, this@FXMetaNode)
|
FXMetaNode(token, this@FXMetaNode)
|
||||||
} else {
|
} else {
|
||||||
FXMetaValue(token, this@FXMetaNode)
|
FXMetaValue(token, this@FXMetaNode)
|
||||||
@ -134,7 +134,7 @@ class FXMetaNode<M : MetaNode<M>>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class FXMetaValue<M : MetaNode<M>>(
|
public class FXMetaValue<M : TypedMeta<M>>(
|
||||||
override val name: NameToken,
|
override val name: NameToken,
|
||||||
override val parent: FXMetaNode<M>
|
override val parent: FXMetaNode<M>
|
||||||
) : FXMeta<M>() {
|
) : FXMeta<M>() {
|
||||||
@ -151,10 +151,10 @@ public class FXMetaValue<M : MetaNode<M>>(
|
|||||||
//private val innerValueProperty = SimpleObjectProperty(value)
|
//private val innerValueProperty = SimpleObjectProperty(value)
|
||||||
|
|
||||||
public val valueProperty = descriptorProperty.objectBinding { descriptor ->
|
public val valueProperty = descriptorProperty.objectBinding { descriptor ->
|
||||||
parent.node[name].value ?: descriptor?.default
|
parent.node?.get(name).value ?: descriptor?.default
|
||||||
}
|
}
|
||||||
|
|
||||||
override val hasValue: ObservableBooleanValue = parent.nodeProperty.booleanBinding { it[name] != null }
|
override val hasValue: ObservableBooleanValue = parent.nodeProperty.booleanBinding { it?.get(name) != null }
|
||||||
|
|
||||||
public val value by valueProperty
|
public val value by valueProperty
|
||||||
|
|
||||||
@ -169,12 +169,12 @@ public fun <M : MutableMeta<M>> FXMetaNode<M>.remove(name: NameToken) {
|
|||||||
private fun <M : MutableMeta<M>> M.createEmptyNode(token: NameToken, append: Boolean): M {
|
private fun <M : MutableMeta<M>> M.createEmptyNode(token: NameToken, append: Boolean): M {
|
||||||
return if (append && token.hasIndex()) {
|
return if (append && token.hasIndex()) {
|
||||||
val name = token.asName()
|
val name = token.asName()
|
||||||
val index = (getIndexed(name).keys.mapNotNull { it.toIntOrNull() }.max() ?: -1) + 1
|
val index = (getIndexed(name).keys.mapNotNull { it?.toIntOrNull() }.maxOrNull() ?: -1) + 1
|
||||||
val newName = name.withIndex(index.toString())
|
val newName = name.withIndex(index.toString())
|
||||||
set(newName, Meta.EMPTY)
|
set(newName, Meta.EMPTY)
|
||||||
get(newName).node!!
|
get(newName).node!!
|
||||||
} else {
|
} else {
|
||||||
this.setNode(token.asName(), Meta.EMPTY)
|
this.set(token.asName(), Meta.EMPTY)
|
||||||
//FIXME possible concurrency bug
|
//FIXME possible concurrency bug
|
||||||
get(token).node!!
|
get(token).node!!
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,8 @@
|
|||||||
package hep.dataforge.vision.editor
|
package hep.dataforge.vision.editor
|
||||||
|
|
||||||
import hep.dataforge.meta.Config
|
import hep.dataforge.meta.*
|
||||||
import hep.dataforge.meta.Meta
|
|
||||||
import hep.dataforge.meta.descriptors.NodeDescriptor
|
import hep.dataforge.meta.descriptors.NodeDescriptor
|
||||||
import hep.dataforge.meta.update
|
import hep.dataforge.vision.*
|
||||||
import hep.dataforge.vision.Vision
|
|
||||||
import hep.dataforge.vision.getStyle
|
|
||||||
import hep.dataforge.vision.setProperty
|
|
||||||
import javafx.beans.binding.Binding
|
import javafx.beans.binding.Binding
|
||||||
import javafx.beans.property.SimpleObjectProperty
|
import javafx.beans.property.SimpleObjectProperty
|
||||||
import javafx.scene.Node
|
import javafx.scene.Node
|
||||||
@ -23,8 +19,8 @@ class VisualObjectEditorFragment(val selector: (Vision) -> Meta) : Fragment() {
|
|||||||
constructor(
|
constructor(
|
||||||
item: Vision?,
|
item: Vision?,
|
||||||
descriptor: NodeDescriptor?,
|
descriptor: NodeDescriptor?,
|
||||||
selector: (Vision) -> Config = { it.config }
|
selector: (Vision) -> MutableItemProvider = { it.allProperties() },
|
||||||
) : this(selector) {
|
) : this({ it.describedProperties }) {
|
||||||
this.item = item
|
this.item = item
|
||||||
this.descriptorProperty.set(descriptor)
|
this.descriptorProperty.set(descriptor)
|
||||||
}
|
}
|
||||||
|
@ -43,9 +43,9 @@ class FX3DPlugin : AbstractPlugin() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun buildNode(obj: Solid): Node {
|
fun buildNode(obj: Solid): Node {
|
||||||
val binding = VisualObjectFXBinding(obj)
|
val binding = VisualObjectFXBinding(this, obj)
|
||||||
return when (obj) {
|
return when (obj) {
|
||||||
is SolidReference -> referenceFactory(obj, binding)
|
is SolidReferenceGroup -> referenceFactory(obj, binding)
|
||||||
is SolidGroup -> {
|
is SolidGroup -> {
|
||||||
Group(obj.children.mapNotNull { (token, obj) ->
|
Group(obj.children.mapNotNull { (token, obj) ->
|
||||||
(obj as? Solid)?.let {
|
(obj as? Solid)?.let {
|
||||||
@ -71,7 +71,7 @@ class FX3DPlugin : AbstractPlugin() {
|
|||||||
is PolyLine -> PolyLine3D(
|
is PolyLine -> PolyLine3D(
|
||||||
obj.points.map { Point3D(it.x, it.y, it.z) },
|
obj.points.map { Point3D(it.x, it.y, it.z) },
|
||||||
obj.thickness.toFloat(),
|
obj.thickness.toFloat(),
|
||||||
obj.getProperty(SolidMaterial.MATERIAL_COLOR_KEY)?.color()
|
obj.getProperty(SolidMaterial.MATERIAL_COLOR_KEY, inherit = true)?.color()
|
||||||
).apply {
|
).apply {
|
||||||
this.meshView.cullFace = CullFace.FRONT
|
this.meshView.cullFace = CullFace.FRONT
|
||||||
}
|
}
|
||||||
@ -130,7 +130,7 @@ class FX3DPlugin : AbstractPlugin() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object : PluginFactory<FX3DPlugin> {
|
companion object : PluginFactory<FX3DPlugin> {
|
||||||
override val tag = PluginTag("visual.fx3D", PluginTag.DATAFORGE_GROUP)
|
override val tag = PluginTag("vision.fx3D", PluginTag.DATAFORGE_GROUP)
|
||||||
override val type = FX3DPlugin::class
|
override val type = FX3DPlugin::class
|
||||||
override fun invoke(meta: Meta, context: Context) = FX3DPlugin()
|
override fun invoke(meta: Meta, context: Context) = FX3DPlugin()
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
package hep.dataforge.vision.solid
|
package hep.dataforge.vision.solid
|
||||||
|
|
||||||
import hep.dataforge.meta.MetaItem
|
import hep.dataforge.meta.*
|
||||||
import hep.dataforge.meta.double
|
|
||||||
import hep.dataforge.meta.get
|
|
||||||
import hep.dataforge.meta.int
|
|
||||||
import hep.dataforge.values.ValueType
|
import hep.dataforge.values.ValueType
|
||||||
import hep.dataforge.values.int
|
import hep.dataforge.values.int
|
||||||
import hep.dataforge.values.string
|
import hep.dataforge.values.string
|
||||||
@ -35,9 +32,9 @@ public object FXMaterials {
|
|||||||
* Infer color based on meta item
|
* Infer color based on meta item
|
||||||
* @param opacity default opacity
|
* @param opacity default opacity
|
||||||
*/
|
*/
|
||||||
public fun MetaItem<*>.color(opacity: Double = 1.0): Color {
|
public fun MetaItem.color(opacity: Double = 1.0): Color {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
is MetaItem.ValueItem -> if (this.value.type == ValueType.NUMBER) {
|
is ValueItem -> if (this.value.type == ValueType.NUMBER) {
|
||||||
val int = value.int
|
val int = value.int
|
||||||
val red = int and 0x00ff0000 shr 16
|
val red = int and 0x00ff0000 shr 16
|
||||||
val green = int and 0x0000ff00 shr 8
|
val green = int and 0x0000ff00 shr 8
|
||||||
@ -46,7 +43,7 @@ public fun MetaItem<*>.color(opacity: Double = 1.0): Color {
|
|||||||
} else {
|
} else {
|
||||||
Color.web(this.value.string)
|
Color.web(this.value.string)
|
||||||
}
|
}
|
||||||
is MetaItem.NodeItem -> {
|
is NodeItem -> {
|
||||||
Color.rgb(
|
Color.rgb(
|
||||||
node[Colors.RED_KEY]?.int ?: 0,
|
node[Colors.RED_KEY]?.int ?: 0,
|
||||||
node[Colors.GREEN_KEY]?.int ?: 0,
|
node[Colors.GREEN_KEY]?.int ?: 0,
|
||||||
@ -60,11 +57,11 @@ public fun MetaItem<*>.color(opacity: Double = 1.0): Color {
|
|||||||
/**
|
/**
|
||||||
* Infer FX material based on meta item
|
* Infer FX material based on meta item
|
||||||
*/
|
*/
|
||||||
public fun MetaItem<*>?.material(): Material {
|
public fun MetaItem?.material(): Material {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
null -> FXMaterials.GREY
|
null -> FXMaterials.GREY
|
||||||
is MetaItem.ValueItem -> PhongMaterial(color())
|
is ValueItem -> PhongMaterial(color())
|
||||||
is MetaItem.NodeItem -> PhongMaterial().apply {
|
is NodeItem -> PhongMaterial().apply {
|
||||||
val opacity = node[SolidMaterial.OPACITY_KEY].double ?: 1.0
|
val opacity = node[SolidMaterial.OPACITY_KEY].double ?: 1.0
|
||||||
diffuseColor = node[SolidMaterial.COLOR_KEY]?.color(opacity) ?: Color.DARKGREY
|
diffuseColor = node[SolidMaterial.COLOR_KEY]?.color(opacity) ?: Color.DARKGREY
|
||||||
specularColor = node[SolidMaterial.SPECULAR_COLOR_KEY]?.color(opacity) ?: Color.WHITE
|
specularColor = node[SolidMaterial.SPECULAR_COLOR_KEY]?.color(opacity) ?: Color.WHITE
|
||||||
|
@ -6,15 +6,15 @@ import javafx.scene.Group
|
|||||||
import javafx.scene.Node
|
import javafx.scene.Node
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
class FXReferenceFactory(val plugin: FX3DPlugin) : FX3DFactory<SolidReference> {
|
class FXReferenceFactory(val plugin: FX3DPlugin) : FX3DFactory<SolidReferenceGroup> {
|
||||||
override val type: KClass<in SolidReference> get() = SolidReference::class
|
override val type: KClass<in SolidReferenceGroup> get() = SolidReferenceGroup::class
|
||||||
|
|
||||||
override fun invoke(obj: SolidReference, binding: VisualObjectFXBinding): Node {
|
override fun invoke(obj: SolidReferenceGroup, binding: VisualObjectFXBinding): Node {
|
||||||
val prototype = obj.prototype
|
val prototype = obj.prototype
|
||||||
val node = plugin.buildNode(prototype)
|
val node = plugin.buildNode(prototype)
|
||||||
|
|
||||||
obj.onPropertyChange(this) { name->
|
obj.onPropertyChange(plugin.context) { name->
|
||||||
if (name.firstOrNull()?.body == SolidReference.REFERENCE_CHILD_PROPERTY_PREFIX) {
|
if (name.firstOrNull()?.body == SolidReferenceGroup.REFERENCE_CHILD_PROPERTY_PREFIX) {
|
||||||
val childName = name.firstOrNull()?.index?.toName() ?: error("Wrong syntax for reference child property: '$name'")
|
val childName = name.firstOrNull()?.index?.toName() ?: error("Wrong syntax for reference child property: '$name'")
|
||||||
val propertyName = name.cutFirst()
|
val propertyName = name.cutFirst()
|
||||||
val referenceChild = obj[childName] ?: error("Reference child with name '$childName' not found")
|
val referenceChild = obj[childName] ?: error("Reference child with name '$childName' not found")
|
||||||
|
@ -12,11 +12,11 @@ import tornadofx.*
|
|||||||
/**
|
/**
|
||||||
* A caching binding collection for [Vision] properties
|
* A caching binding collection for [Vision] properties
|
||||||
*/
|
*/
|
||||||
class VisualObjectFXBinding(val obj: Vision) {
|
class VisualObjectFXBinding(val fx: FX3DPlugin, val obj: Vision) {
|
||||||
private val bindings = HashMap<Name, ObjectBinding<MetaItem<*>?>>()
|
private val bindings = HashMap<Name, ObjectBinding<MetaItem?>>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
obj.onPropertyChange(this) { name ->
|
obj.onPropertyChange(fx.context) { name ->
|
||||||
bindings.filter { it.key.startsWith(name) }.forEach { entry ->
|
bindings.filter { it.key.startsWith(name) }.forEach { entry ->
|
||||||
Platform.runLater {
|
Platform.runLater {
|
||||||
entry.value.invalidate()
|
entry.value.invalidate()
|
||||||
@ -31,10 +31,10 @@ class VisualObjectFXBinding(val obj: Vision) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun get(key: Name): ObjectBinding<MetaItem<*>?> {
|
operator fun get(key: Name): ObjectBinding<MetaItem?> {
|
||||||
return bindings.getOrPut(key) {
|
return bindings.getOrPut(key) {
|
||||||
object : ObjectBinding<MetaItem<*>?>() {
|
object : ObjectBinding<MetaItem?>() {
|
||||||
override fun computeValue(): MetaItem<*>? = obj.getProperty(key)
|
override fun computeValue(): MetaItem? = obj.getProperty(key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -42,19 +42,19 @@ class VisualObjectFXBinding(val obj: Vision) {
|
|||||||
operator fun get(key: String) = get(key.toName())
|
operator fun get(key: String) = get(key.toName())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ObjectBinding<MetaItem<*>?>.value() = objectBinding { it.value }
|
fun ObjectBinding<MetaItem?>.value() = objectBinding { it.value }
|
||||||
fun ObjectBinding<MetaItem<*>?>.string() = stringBinding { it.string }
|
fun ObjectBinding<MetaItem?>.string() = stringBinding { it.string }
|
||||||
fun ObjectBinding<MetaItem<*>?>.number() = objectBinding { it.number }
|
fun ObjectBinding<MetaItem?>.number() = objectBinding { it.number }
|
||||||
fun ObjectBinding<MetaItem<*>?>.double() = objectBinding { it.double }
|
fun ObjectBinding<MetaItem?>.double() = objectBinding { it.double }
|
||||||
fun ObjectBinding<MetaItem<*>?>.float() = objectBinding { it.float }
|
fun ObjectBinding<MetaItem?>.float() = objectBinding { it.float }
|
||||||
fun ObjectBinding<MetaItem<*>?>.int() = objectBinding { it.int }
|
fun ObjectBinding<MetaItem?>.int() = objectBinding { it.int }
|
||||||
fun ObjectBinding<MetaItem<*>?>.long() = objectBinding { it.long }
|
fun ObjectBinding<MetaItem?>.long() = objectBinding { it.long }
|
||||||
fun ObjectBinding<MetaItem<*>?>.node() = objectBinding { it.node }
|
fun ObjectBinding<MetaItem?>.node() = objectBinding { it.node }
|
||||||
|
|
||||||
fun ObjectBinding<MetaItem<*>?>.string(default: String) = stringBinding { it.string ?: default }
|
fun ObjectBinding<MetaItem?>.string(default: String) = stringBinding { it.string ?: default }
|
||||||
fun ObjectBinding<MetaItem<*>?>.double(default: Double) = doubleBinding { it.double ?: default }
|
fun ObjectBinding<MetaItem?>.double(default: Double) = doubleBinding { it.double ?: default }
|
||||||
fun ObjectBinding<MetaItem<*>?>.float(default: Float) = floatBinding { it.float ?: default }
|
fun ObjectBinding<MetaItem?>.float(default: Float) = floatBinding { it.float ?: default }
|
||||||
fun ObjectBinding<MetaItem<*>?>.int(default: Int) = integerBinding { it.int ?: default }
|
fun ObjectBinding<MetaItem?>.int(default: Int) = integerBinding { it.int ?: default }
|
||||||
fun ObjectBinding<MetaItem<*>?>.long(default: Long) = longBinding { it.long ?: default }
|
fun ObjectBinding<MetaItem?>.long(default: Long) = longBinding { it.long ?: default }
|
||||||
|
|
||||||
fun <T> ObjectBinding<MetaItem<*>?>.transform(transform: (MetaItem<*>) -> T) = objectBinding { it?.let(transform) }
|
fun <T> ObjectBinding<MetaItem?>.transform(transform: (MetaItem) -> T) = objectBinding { it?.let(transform) }
|
||||||
|
@ -2,12 +2,12 @@ package hep.dataforge.vision.gdml
|
|||||||
|
|
||||||
import hep.dataforge.meta.Meta
|
import hep.dataforge.meta.Meta
|
||||||
import hep.dataforge.meta.MetaBuilder
|
import hep.dataforge.meta.MetaBuilder
|
||||||
import hep.dataforge.meta.set
|
|
||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.Name
|
||||||
import hep.dataforge.names.asName
|
import hep.dataforge.names.asName
|
||||||
import hep.dataforge.names.plus
|
import hep.dataforge.names.plus
|
||||||
import hep.dataforge.names.toName
|
import hep.dataforge.names.toName
|
||||||
import hep.dataforge.vision.set
|
import hep.dataforge.vision.set
|
||||||
|
import hep.dataforge.vision.setProperty
|
||||||
import hep.dataforge.vision.solid.*
|
import hep.dataforge.vision.solid.*
|
||||||
import hep.dataforge.vision.solid.SolidMaterial.Companion.MATERIAL_COLOR_KEY
|
import hep.dataforge.vision.solid.SolidMaterial.Companion.MATERIAL_COLOR_KEY
|
||||||
import hep.dataforge.vision.styleSheet
|
import hep.dataforge.vision.styleSheet
|
||||||
@ -38,7 +38,6 @@ public class GDMLTransformerSettings {
|
|||||||
|
|
||||||
public var solidAction: (GDMLSolid) -> Action = { Action.PROTOTYPE }
|
public var solidAction: (GDMLSolid) -> Action = { Action.PROTOTYPE }
|
||||||
public var volumeAction: (GDMLGroup) -> Action = { Action.PROTOTYPE }
|
public var volumeAction: (GDMLGroup) -> Action = { Action.PROTOTYPE }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class GDMLTransformer(val settings: GDMLTransformerSettings) {
|
private class GDMLTransformer(val settings: GDMLTransformerSettings) {
|
||||||
@ -51,13 +50,13 @@ private class GDMLTransformer(val settings: GDMLTransformerSettings) {
|
|||||||
private val proto = SolidGroup()
|
private val proto = SolidGroup()
|
||||||
|
|
||||||
private val solids = proto.group(solidsName) {
|
private val solids = proto.group(solidsName) {
|
||||||
config["edges.enabled"] = false
|
setProperty("edges.enabled", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private val referenceStore = HashMap<Name, MutableList<SolidReference>>()
|
private val referenceStore = HashMap<Name, MutableList<SolidReferenceGroup>>()
|
||||||
|
|
||||||
private fun proxySolid(root: GDML, group: SolidGroup, solid: GDMLSolid, name: String): SolidReference {
|
private fun proxySolid(root: GDML, group: SolidGroup, solid: GDMLSolid, name: String): SolidReferenceGroup {
|
||||||
val templateName = solidsName + name
|
val templateName = solidsName + name
|
||||||
if (proto[templateName] == null) {
|
if (proto[templateName] == null) {
|
||||||
solids.addSolid(root, solid, name)
|
solids.addSolid(root, solid, name)
|
||||||
@ -67,7 +66,7 @@ private class GDMLTransformer(val settings: GDMLTransformerSettings) {
|
|||||||
return ref
|
return ref
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun proxyVolume(root: GDML, group: SolidGroup, physVolume: GDMLPhysVolume, volume: GDMLGroup): SolidReference {
|
private fun proxyVolume(root: GDML, group: SolidGroup, physVolume: GDMLPhysVolume, volume: GDMLGroup): SolidReferenceGroup {
|
||||||
val templateName = volumesName + volume.name.asName()
|
val templateName = volumesName + volume.name.asName()
|
||||||
if (proto[templateName] == null) {
|
if (proto[templateName] == null) {
|
||||||
proto[templateName] = volume(root, volume)
|
proto[templateName] = volume(root, volume)
|
||||||
@ -324,7 +323,7 @@ private class GDMLTransformer(val settings: GDMLTransformerSettings) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun finalize(final: SolidGroup): SolidGroup {
|
private fun finalize(final: SolidGroup): SolidGroup {
|
||||||
//final.prototypes = proto
|
//final.prototypes = proto
|
||||||
final.useStyle("GDML") {
|
final.useStyle("GDML") {
|
||||||
Solid.ROTATION_ORDER_KEY put RotationOrder.ZXY
|
Solid.ROTATION_ORDER_KEY put RotationOrder.ZXY
|
||||||
|
@ -1,17 +1,9 @@
|
|||||||
package hep.dataforge.vision.gdml
|
package hep.dataforge.vision.gdml
|
||||||
|
|
||||||
import hep.dataforge.meta.DFExperimental
|
import hep.dataforge.meta.DFExperimental
|
||||||
import hep.dataforge.meta.sequence
|
import hep.dataforge.meta.itemSequence
|
||||||
import hep.dataforge.meta.set
|
import hep.dataforge.vision.Vision
|
||||||
import hep.dataforge.names.Name
|
|
||||||
import hep.dataforge.names.toName
|
|
||||||
import hep.dataforge.vision.*
|
|
||||||
import hep.dataforge.vision.solid.*
|
import hep.dataforge.vision.solid.*
|
||||||
import hep.dataforge.vision.visitor.VisionVisitor
|
|
||||||
import kotlinx.coroutines.CompletableDeferred
|
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.coroutineScope
|
|
||||||
import mu.KotlinLogging
|
|
||||||
|
|
||||||
public expect class Counter() {
|
public expect class Counter() {
|
||||||
public fun get(): Int
|
public fun get(): Int
|
||||||
@ -24,6 +16,7 @@ private fun Point3D?.safePlus(other: Point3D?): Point3D? = if (this == null && o
|
|||||||
(this ?: Point3D(0, 0, 0)) + (other ?: Point3D(0, 0, 0))
|
(this ?: Point3D(0, 0, 0)) + (other ?: Point3D(0, 0, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@DFExperimental
|
||||||
internal fun Vision.updateFrom(other: Vision): Vision {
|
internal fun Vision.updateFrom(other: Vision): Vision {
|
||||||
if (this is Solid && other is Solid) {
|
if (this is Solid && other is Solid) {
|
||||||
position = position.safePlus(other.position)
|
position = position.safePlus(other.position)
|
||||||
@ -33,9 +26,9 @@ internal fun Vision.updateFrom(other: Vision): Vision {
|
|||||||
scaleY = scaleY.toDouble() * other.scaleY.toDouble()
|
scaleY = scaleY.toDouble() * other.scaleY.toDouble()
|
||||||
scaleZ = scaleZ.toDouble() * other.scaleZ.toDouble()
|
scaleZ = scaleZ.toDouble() * other.scaleZ.toDouble()
|
||||||
}
|
}
|
||||||
other.properties?.sequence()?.forEach { (name, item) ->
|
other.meta.itemSequence().forEach { (name, item) ->
|
||||||
if (properties?.getItem(name) == null) {
|
if (getProperty(name) == null) {
|
||||||
config[name] = item
|
setProperty(name, item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
20
visionforge-plotly/build.gradle.kts
Normal file
20
visionforge-plotly/build.gradle.kts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
plugins {
|
||||||
|
id("ru.mipt.npm.mpp")
|
||||||
|
}
|
||||||
|
|
||||||
|
kscience {
|
||||||
|
useSerialization()
|
||||||
|
}
|
||||||
|
|
||||||
|
val plotlyVersion = "0.3.1-dev"
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
sourceSets {
|
||||||
|
commonMain {
|
||||||
|
dependencies {
|
||||||
|
api(project(":visionforge-core"))
|
||||||
|
api("kscience.plotlykt:plotlykt-core:${plotlyVersion}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
package hep.dataforge.vision.plotly
|
||||||
|
|
||||||
|
import hep.dataforge.meta.DFExperimental
|
||||||
|
import hep.dataforge.vision.VisionBase
|
||||||
|
import hep.dataforge.vision.html.VisionOutput
|
||||||
|
import kscience.plotly.Plot
|
||||||
|
import kscience.plotly.Plotly
|
||||||
|
|
||||||
|
public class VisionOfPlotly(public val plot: Plot): VisionBase(plot.config)
|
||||||
|
|
||||||
|
@DFExperimental
|
||||||
|
public inline fun VisionOutput.plotly(block: Plot.() -> Unit): VisionOfPlotly = VisionOfPlotly(Plotly.plot(block))
|
@ -0,0 +1,33 @@
|
|||||||
|
package hep.dataforge.vision.plotly
|
||||||
|
|
||||||
|
import hep.dataforge.context.AbstractPlugin
|
||||||
|
import hep.dataforge.context.Context
|
||||||
|
import hep.dataforge.context.PluginFactory
|
||||||
|
import hep.dataforge.context.PluginTag
|
||||||
|
import hep.dataforge.meta.Meta
|
||||||
|
import hep.dataforge.vision.Vision
|
||||||
|
import hep.dataforge.vision.client.ElementVisionRenderer
|
||||||
|
import kscience.plotly.PlotlyConfig
|
||||||
|
import kscience.plotly.plot
|
||||||
|
import org.w3c.dom.Element
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
public class PlotlyPlugin : AbstractPlugin(), ElementVisionRenderer {
|
||||||
|
|
||||||
|
override val tag: PluginTag get() = Companion.tag
|
||||||
|
|
||||||
|
override fun rateVision(vision: Vision): Int =
|
||||||
|
if (vision is VisionOfPlotly) ElementVisionRenderer.DEFAULT_RATING else ElementVisionRenderer.ZERO_RATING
|
||||||
|
|
||||||
|
override fun render(element: Element, vision: Vision, meta: Meta) {
|
||||||
|
val plot = (vision as? VisionOfPlotly)?.plot ?: error("Only VisionOfPlotly visions are supported")
|
||||||
|
val config = PlotlyConfig.read(meta)
|
||||||
|
element.plot(plot, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
public companion object : PluginFactory<PlotlyPlugin> {
|
||||||
|
override val tag: PluginTag = PluginTag("vision.plotly", PluginTag.DATAFORGE_GROUP)
|
||||||
|
override val type: KClass<PlotlyPlugin> = PlotlyPlugin::class
|
||||||
|
override fun invoke(meta: Meta, context: Context): PlotlyPlugin = PlotlyPlugin()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package hep.dataforge.vision.plotly
|
||||||
|
|
||||||
|
//public fun main() {
|
||||||
|
// val visionContext: Context = Global.context("vision-client")
|
||||||
|
//
|
||||||
|
// //Loading three-js renderer
|
||||||
|
// val threePlugin = visionContext.plugins.fetch(PlotlyPlugin)
|
||||||
|
//
|
||||||
|
// val clientManager = visionContext.plugins.fetch(VisionClient)
|
||||||
|
//
|
||||||
|
// //Fetch from server and render visions for all outputs
|
||||||
|
// window.onload = {
|
||||||
|
// clientManager.renderAllVisions()
|
||||||
|
// }
|
||||||
|
//}
|
@ -41,6 +41,13 @@ import java.awt.Desktop
|
|||||||
import java.net.URI
|
import java.net.URI
|
||||||
import kotlin.time.milliseconds
|
import kotlin.time.milliseconds
|
||||||
|
|
||||||
|
public enum class VisionServerDataMode {
|
||||||
|
EMBED,
|
||||||
|
FETCH,
|
||||||
|
CONNECT
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A ktor plugin container with given [routing]
|
* A ktor plugin container with given [routing]
|
||||||
*/
|
*/
|
||||||
@ -52,6 +59,7 @@ public class VisionServer internal constructor(
|
|||||||
override val config: Config = Config()
|
override val config: Config = Config()
|
||||||
public var updateInterval: Long by config.long(300, key = UPDATE_INTERVAL_KEY)
|
public var updateInterval: Long by config.long(300, key = UPDATE_INTERVAL_KEY)
|
||||||
public var cacheFragments: Boolean by config.boolean(true)
|
public var cacheFragments: Boolean by config.boolean(true)
|
||||||
|
public var dataMode: VisionServerDataMode = VisionServerDataMode.CONNECT
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* a list of headers that should be applied to all pages
|
* a list of headers that should be applied to all pages
|
||||||
@ -72,10 +80,23 @@ public class VisionServer internal constructor(
|
|||||||
val consumer = object : VisionTagConsumer<Any?>(consumer) {
|
val consumer = object : VisionTagConsumer<Any?>(consumer) {
|
||||||
override fun DIV.renderVision(name: Name, vision: Vision, outputMeta: Meta) {
|
override fun DIV.renderVision(name: Name, vision: Vision, outputMeta: Meta) {
|
||||||
visionMap[name] = vision
|
visionMap[name] = vision
|
||||||
|
// Toggle update mode
|
||||||
// Toggle updates
|
when (dataMode) {
|
||||||
attributes[OUTPUT_FETCH_ATTRIBUTE] = "true"
|
VisionServerDataMode.EMBED -> {
|
||||||
attributes[OUTPUT_CONNECT_ATTRIBUTE] = "true"
|
script {
|
||||||
|
attributes["class"] = OUTPUT_DATA_CLASS
|
||||||
|
unsafe {
|
||||||
|
+visionManager.encodeToString(vision)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VisionServerDataMode.FETCH -> {
|
||||||
|
attributes[OUTPUT_FETCH_ATTRIBUTE] = "auto"
|
||||||
|
}
|
||||||
|
VisionServerDataMode.CONNECT -> {
|
||||||
|
attributes[OUTPUT_CONNECT_ATTRIBUTE] = "auto"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,10 +131,11 @@ public class VisionServer internal constructor(
|
|||||||
|
|
||||||
application.log.debug("Opened server socket for $name")
|
application.log.debug("Opened server socket for $name")
|
||||||
val vision: Vision = visions[name.toName()] ?: error("Plot with id='$name' not registered")
|
val vision: Vision = visions[name.toName()] ?: error("Plot with id='$name' not registered")
|
||||||
|
|
||||||
try {
|
try {
|
||||||
withContext(visionManager.context.coroutineContext) {
|
withContext(visionManager.context.coroutineContext) {
|
||||||
vision.flowChanges(visionManager, updateInterval.milliseconds).collect { update ->
|
vision.flowChanges(visionManager, updateInterval.milliseconds).collect { update ->
|
||||||
val json = VisionManager.defaultJson.encodeToString(
|
val json = visionManager.jsonFormat.encodeToString(
|
||||||
VisionChange.serializer(),
|
VisionChange.serializer(),
|
||||||
update
|
update
|
||||||
)
|
)
|
||||||
|
@ -0,0 +1,51 @@
|
|||||||
|
package hep.dataforge.vision.solid
|
||||||
|
|
||||||
|
import hep.dataforge.meta.MutableItemProvider
|
||||||
|
import hep.dataforge.meta.set
|
||||||
|
import hep.dataforge.meta.value
|
||||||
|
import hep.dataforge.names.Name
|
||||||
|
import hep.dataforge.values.Value
|
||||||
|
import hep.dataforge.values.asValue
|
||||||
|
import hep.dataforge.values.string
|
||||||
|
import hep.dataforge.vision.Colors
|
||||||
|
import hep.dataforge.vision.VisionBuilder
|
||||||
|
|
||||||
|
@VisionBuilder
|
||||||
|
public class ColorAccessor(private val parent: MutableItemProvider, private val colorKey: Name) {
|
||||||
|
public var value: Value?
|
||||||
|
get() = parent.getItem(colorKey).value
|
||||||
|
set(value) {
|
||||||
|
parent[colorKey] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var ColorAccessor?.string: String?
|
||||||
|
get() = this?.value?.string
|
||||||
|
set(value) {
|
||||||
|
this?.value = value?.asValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set [webcolor](https://en.wikipedia.org/wiki/Web_colors) as string
|
||||||
|
*/
|
||||||
|
public operator fun ColorAccessor?.invoke(webColor: String) {
|
||||||
|
this?.value = webColor.asValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set color as RGB integer
|
||||||
|
*/
|
||||||
|
public operator fun ColorAccessor?.invoke(rgb: Int) {
|
||||||
|
this?.value = Colors.rgbToString(rgb).asValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set color as RGB
|
||||||
|
*/
|
||||||
|
public operator fun ColorAccessor?.invoke(r: UByte, g: UByte, b: UByte) {
|
||||||
|
this?.value = Colors.rgbToString(r, g, b).asValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun ColorAccessor?.clear() {
|
||||||
|
this?.value = null
|
||||||
|
}
|
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
package hep.dataforge.vision.solid
|
package hep.dataforge.vision.solid
|
||||||
|
|
||||||
import hep.dataforge.meta.update
|
import hep.dataforge.meta.update
|
||||||
@ -18,7 +17,7 @@ public enum class CompositeType {
|
|||||||
public class Composite(
|
public class Composite(
|
||||||
public val compositeType: CompositeType,
|
public val compositeType: CompositeType,
|
||||||
public val first: Solid,
|
public val first: Solid,
|
||||||
public val second: Solid
|
public val second: Solid,
|
||||||
) : SolidBase(), Solid, VisionGroup {
|
) : SolidBase(), Solid, VisionGroup {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@ -34,25 +33,25 @@ public class Composite(
|
|||||||
public inline fun VisionContainerBuilder<Solid>.composite(
|
public inline fun VisionContainerBuilder<Solid>.composite(
|
||||||
type: CompositeType,
|
type: CompositeType,
|
||||||
name: String = "",
|
name: String = "",
|
||||||
builder: SolidGroup.() -> Unit
|
builder: SolidGroup.() -> Unit,
|
||||||
): Composite {
|
): Composite {
|
||||||
val group = SolidGroup().apply(builder)
|
val group = SolidGroup().apply(builder)
|
||||||
val children = group.children.values.filterIsInstance<Solid>()
|
val children = group.children.values.filterIsInstance<Solid>()
|
||||||
if (children.size != 2) error("Composite requires exactly two children")
|
if (children.size != 2) error("Composite requires exactly two children")
|
||||||
return Composite(type, children[0], children[1]).also {
|
return Composite(type, children[0], children[1]).also { composite ->
|
||||||
it.config.update(group.config)
|
composite.configure {
|
||||||
//it.material = group.material
|
update(group.meta)
|
||||||
|
}
|
||||||
if (group.position != null) {
|
if (group.position != null) {
|
||||||
it.position = group.position
|
composite.position = group.position
|
||||||
}
|
}
|
||||||
if (group.rotation != null) {
|
if (group.rotation != null) {
|
||||||
it.rotation = group.rotation
|
composite.rotation = group.rotation
|
||||||
}
|
}
|
||||||
if (group.scale != null) {
|
if (group.scale != null) {
|
||||||
it.scale = group.scale
|
composite.scale = group.scale
|
||||||
}
|
}
|
||||||
set(name, it)
|
set(name, composite)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,5 +64,8 @@ public inline fun VisionContainerBuilder<Solid>.subtract(name: String = "", buil
|
|||||||
composite(CompositeType.SUBTRACT, name, builder = builder)
|
composite(CompositeType.SUBTRACT, name, builder = builder)
|
||||||
|
|
||||||
@VisionBuilder
|
@VisionBuilder
|
||||||
public inline fun VisionContainerBuilder<Solid>.intersect(name: String = "", builder: SolidGroup.() -> Unit): Composite =
|
public inline fun VisionContainerBuilder<Solid>.intersect(
|
||||||
|
name: String = "",
|
||||||
|
builder: SolidGroup.() -> Unit,
|
||||||
|
): Composite =
|
||||||
composite(CompositeType.INTERSECT, name, builder = builder)
|
composite(CompositeType.INTERSECT, name, builder = builder)
|
@ -6,7 +6,7 @@ import hep.dataforge.names.asName
|
|||||||
import hep.dataforge.names.plus
|
import hep.dataforge.names.plus
|
||||||
import hep.dataforge.vision.VisionBuilder
|
import hep.dataforge.vision.VisionBuilder
|
||||||
import hep.dataforge.vision.VisionContainerBuilder
|
import hep.dataforge.vision.VisionContainerBuilder
|
||||||
import hep.dataforge.vision.props
|
import hep.dataforge.vision.allProperties
|
||||||
import hep.dataforge.vision.set
|
import hep.dataforge.vision.set
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
@ -16,7 +16,7 @@ import kotlinx.serialization.Serializable
|
|||||||
public class PolyLine(public var points: List<Point3D>) : SolidBase(), Solid {
|
public class PolyLine(public var points: List<Point3D>) : SolidBase(), Solid {
|
||||||
|
|
||||||
//var lineType by string()
|
//var lineType by string()
|
||||||
public var thickness: Number by props().number(1.0, key = SolidMaterial.MATERIAL_KEY + THICKNESS_KEY)
|
public var thickness: Number by allProperties(inherit = false).number(1.0, key = SolidMaterial.MATERIAL_KEY + THICKNESS_KEY)
|
||||||
|
|
||||||
public companion object {
|
public companion object {
|
||||||
public val THICKNESS_KEY: Name = "thickness".asName()
|
public val THICKNESS_KEY: Name = "thickness".asName()
|
||||||
|
@ -1,18 +1,17 @@
|
|||||||
package hep.dataforge.vision.solid
|
package hep.dataforge.vision.solid
|
||||||
|
|
||||||
import hep.dataforge.meta.*
|
import hep.dataforge.meta.boolean
|
||||||
import hep.dataforge.meta.descriptors.NodeDescriptor
|
import hep.dataforge.meta.descriptors.NodeDescriptor
|
||||||
|
import hep.dataforge.meta.enum
|
||||||
|
import hep.dataforge.meta.int
|
||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.Name
|
||||||
import hep.dataforge.names.asName
|
import hep.dataforge.names.asName
|
||||||
import hep.dataforge.names.plus
|
import hep.dataforge.names.plus
|
||||||
import hep.dataforge.values.ValueType
|
import hep.dataforge.values.ValueType
|
||||||
import hep.dataforge.values.asValue
|
import hep.dataforge.values.asValue
|
||||||
import hep.dataforge.vision.Vision
|
import hep.dataforge.vision.*
|
||||||
import hep.dataforge.vision.Vision.Companion.VISIBLE_KEY
|
import hep.dataforge.vision.Vision.Companion.VISIBLE_KEY
|
||||||
import hep.dataforge.vision.VisionBuilder
|
|
||||||
import hep.dataforge.vision.enum
|
|
||||||
import hep.dataforge.vision.layout.Output
|
import hep.dataforge.vision.layout.Output
|
||||||
import hep.dataforge.vision.setProperty
|
|
||||||
import hep.dataforge.vision.solid.Solid.Companion.DETAIL_KEY
|
import hep.dataforge.vision.solid.Solid.Companion.DETAIL_KEY
|
||||||
import hep.dataforge.vision.solid.Solid.Companion.IGNORE_KEY
|
import hep.dataforge.vision.solid.Solid.Companion.IGNORE_KEY
|
||||||
import hep.dataforge.vision.solid.Solid.Companion.LAYER_KEY
|
import hep.dataforge.vision.solid.Solid.Companion.LAYER_KEY
|
||||||
@ -45,13 +44,13 @@ public interface Solid : Vision {
|
|||||||
public val Y_POSITION_KEY: Name = POSITION_KEY + Y_KEY
|
public val Y_POSITION_KEY: Name = POSITION_KEY + Y_KEY
|
||||||
public val Z_POSITION_KEY: Name = POSITION_KEY + Z_KEY
|
public val Z_POSITION_KEY: Name = POSITION_KEY + Z_KEY
|
||||||
|
|
||||||
public val ROTATION: Name = "rotation".asName()
|
public val ROTATION_KEY: Name = "rotation".asName()
|
||||||
|
|
||||||
public val X_ROTATION_KEY: Name = ROTATION + X_KEY
|
public val X_ROTATION_KEY: Name = ROTATION_KEY + X_KEY
|
||||||
public val Y_ROTATION_KEY: Name = ROTATION + Y_KEY
|
public val Y_ROTATION_KEY: Name = ROTATION_KEY + Y_KEY
|
||||||
public val Z_ROTATION_KEY: Name = ROTATION + Z_KEY
|
public val Z_ROTATION_KEY: Name = ROTATION_KEY + Z_KEY
|
||||||
|
|
||||||
public val ROTATION_ORDER_KEY: Name = ROTATION + "order"
|
public val ROTATION_ORDER_KEY: Name = ROTATION_KEY + "order"
|
||||||
|
|
||||||
public val SCALE_KEY: Name = "scale".asName()
|
public val SCALE_KEY: Name = "scale".asName()
|
||||||
|
|
||||||
@ -62,6 +61,7 @@ public interface Solid : Vision {
|
|||||||
public val descriptor: NodeDescriptor by lazy {
|
public val descriptor: NodeDescriptor by lazy {
|
||||||
NodeDescriptor {
|
NodeDescriptor {
|
||||||
value(VISIBLE_KEY) {
|
value(VISIBLE_KEY) {
|
||||||
|
inherited = false
|
||||||
type(ValueType.BOOLEAN)
|
type(ValueType.BOOLEAN)
|
||||||
default(true)
|
default(true)
|
||||||
}
|
}
|
||||||
@ -70,11 +70,14 @@ public interface Solid : Vision {
|
|||||||
value(Vision.STYLE_KEY) {
|
value(Vision.STYLE_KEY) {
|
||||||
type(ValueType.STRING)
|
type(ValueType.STRING)
|
||||||
multiple = true
|
multiple = true
|
||||||
|
hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
item(SolidMaterial.MATERIAL_KEY.toString(), SolidMaterial.descriptor)
|
item(SolidMaterial.MATERIAL_KEY.toString(), SolidMaterial.descriptor)
|
||||||
|
|
||||||
enum(ROTATION_ORDER_KEY, default = RotationOrder.XYZ)
|
enum(ROTATION_ORDER_KEY, default = RotationOrder.XYZ) {
|
||||||
|
hide()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,7 +85,7 @@ public interface Solid : Vision {
|
|||||||
if (first.position != second.position) return false
|
if (first.position != second.position) return false
|
||||||
if (first.rotation != second.rotation) return false
|
if (first.rotation != second.rotation) return false
|
||||||
if (first.scale != second.scale) return false
|
if (first.scale != second.scale) return false
|
||||||
if (first.properties != second.properties) return false
|
if (first.meta != second.meta) return false
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,7 +93,7 @@ public interface Solid : Vision {
|
|||||||
var result = +(solid.position?.hashCode() ?: 0)
|
var result = +(solid.position?.hashCode() ?: 0)
|
||||||
result = 31 * result + (solid.rotation?.hashCode() ?: 0)
|
result = 31 * result + (solid.rotation?.hashCode() ?: 0)
|
||||||
result = 31 * result + (solid.scale?.hashCode() ?: 0)
|
result = 31 * result + (solid.scale?.hashCode() ?: 0)
|
||||||
result = 31 * result + (solid.properties?.hashCode() ?: 0)
|
result = 31 * result + solid.allProperties().hashCode()
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -100,9 +103,9 @@ public interface Solid : Vision {
|
|||||||
* Get the layer number this solid belongs to. Return 0 if layer is not defined.
|
* Get the layer number this solid belongs to. Return 0 if layer is not defined.
|
||||||
*/
|
*/
|
||||||
public var Solid.layer: Int
|
public var Solid.layer: Int
|
||||||
get() = properties?.getItem(LAYER_KEY).int ?: 0
|
get() = allProperties().getItem(LAYER_KEY).int ?: 0
|
||||||
set(value) {
|
set(value) {
|
||||||
config[LAYER_KEY] = value.asValue()
|
setProperty(LAYER_KEY, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisionBuilder
|
@VisionBuilder
|
||||||
@ -153,21 +156,21 @@ public var Solid.x: Number
|
|||||||
get() = position?.x ?: 0f
|
get() = position?.x ?: 0f
|
||||||
set(value) {
|
set(value) {
|
||||||
position().x = value.toDouble()
|
position().x = value.toDouble()
|
||||||
propertyChanged(Solid.X_POSITION_KEY)
|
asyncNotifyPropertyChange(Solid.X_POSITION_KEY)
|
||||||
}
|
}
|
||||||
|
|
||||||
public var Solid.y: Number
|
public var Solid.y: Number
|
||||||
get() = position?.y ?: 0f
|
get() = position?.y ?: 0f
|
||||||
set(value) {
|
set(value) {
|
||||||
position().y = value.toDouble()
|
position().y = value.toDouble()
|
||||||
propertyChanged(Solid.Y_POSITION_KEY)
|
asyncNotifyPropertyChange(Solid.Y_POSITION_KEY)
|
||||||
}
|
}
|
||||||
|
|
||||||
public var Solid.z: Number
|
public var Solid.z: Number
|
||||||
get() = position?.z ?: 0f
|
get() = position?.z ?: 0f
|
||||||
set(value) {
|
set(value) {
|
||||||
position().z = value.toDouble()
|
position().z = value.toDouble()
|
||||||
propertyChanged(Solid.Z_POSITION_KEY)
|
asyncNotifyPropertyChange(Solid.Z_POSITION_KEY)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Solid.rotation(): Point3D =
|
private fun Solid.rotation(): Point3D =
|
||||||
@ -177,21 +180,21 @@ public var Solid.rotationX: Number
|
|||||||
get() = rotation?.x ?: 0f
|
get() = rotation?.x ?: 0f
|
||||||
set(value) {
|
set(value) {
|
||||||
rotation().x = value.toDouble()
|
rotation().x = value.toDouble()
|
||||||
propertyChanged(Solid.X_ROTATION_KEY)
|
asyncNotifyPropertyChange(Solid.X_ROTATION_KEY)
|
||||||
}
|
}
|
||||||
|
|
||||||
public var Solid.rotationY: Number
|
public var Solid.rotationY: Number
|
||||||
get() = rotation?.y ?: 0f
|
get() = rotation?.y ?: 0f
|
||||||
set(value) {
|
set(value) {
|
||||||
rotation().y = value.toDouble()
|
rotation().y = value.toDouble()
|
||||||
propertyChanged(Solid.Y_ROTATION_KEY)
|
asyncNotifyPropertyChange(Solid.Y_ROTATION_KEY)
|
||||||
}
|
}
|
||||||
|
|
||||||
public var Solid.rotationZ: Number
|
public var Solid.rotationZ: Number
|
||||||
get() = rotation?.z ?: 0f
|
get() = rotation?.z ?: 0f
|
||||||
set(value) {
|
set(value) {
|
||||||
rotation().z = value.toDouble()
|
rotation().z = value.toDouble()
|
||||||
propertyChanged(Solid.Z_ROTATION_KEY)
|
asyncNotifyPropertyChange(Solid.Z_ROTATION_KEY)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Solid.scale(): Point3D =
|
private fun Solid.scale(): Point3D =
|
||||||
@ -201,19 +204,19 @@ public var Solid.scaleX: Number
|
|||||||
get() = scale?.x ?: 1f
|
get() = scale?.x ?: 1f
|
||||||
set(value) {
|
set(value) {
|
||||||
scale().x = value.toDouble()
|
scale().x = value.toDouble()
|
||||||
propertyChanged(Solid.X_SCALE_KEY)
|
asyncNotifyPropertyChange(Solid.X_SCALE_KEY)
|
||||||
}
|
}
|
||||||
|
|
||||||
public var Solid.scaleY: Number
|
public var Solid.scaleY: Number
|
||||||
get() = scale?.y ?: 1f
|
get() = scale?.y ?: 1f
|
||||||
set(value) {
|
set(value) {
|
||||||
scale().y = value.toDouble()
|
scale().y = value.toDouble()
|
||||||
propertyChanged(Solid.Y_SCALE_KEY)
|
asyncNotifyPropertyChange(Solid.Y_SCALE_KEY)
|
||||||
}
|
}
|
||||||
|
|
||||||
public var Solid.scaleZ: Number
|
public var Solid.scaleZ: Number
|
||||||
get() = scale?.z ?: 1f
|
get() = scale?.z ?: 1f
|
||||||
set(value) {
|
set(value) {
|
||||||
scale().z = value.toDouble()
|
scale().z = value.toDouble()
|
||||||
propertyChanged(Solid.Z_SCALE_KEY)
|
asyncNotifyPropertyChange(Solid.Z_SCALE_KEY)
|
||||||
}
|
}
|
@ -35,6 +35,6 @@ internal fun Meta.toVector(default: Float = 0f) = Point3D(
|
|||||||
|
|
||||||
internal fun Solid.updatePosition(meta: Meta?) {
|
internal fun Solid.updatePosition(meta: Meta?) {
|
||||||
meta[Solid.POSITION_KEY].node?.toVector()?.let { position = it }
|
meta[Solid.POSITION_KEY].node?.toVector()?.let { position = it }
|
||||||
meta[Solid.ROTATION].node?.toVector()?.let { rotation = it }
|
meta[Solid.ROTATION_KEY].node?.toVector()?.let { rotation = it }
|
||||||
meta[Solid.SCALE_KEY].node?.toVector(1f)?.let { scale = it }
|
meta[Solid.SCALE_KEY].node?.toVector(1f)?.let { scale = it }
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
package hep.dataforge.vision.solid
|
package hep.dataforge.vision.solid
|
||||||
|
|
||||||
import hep.dataforge.meta.Config
|
import hep.dataforge.meta.MetaItem
|
||||||
import hep.dataforge.meta.descriptors.NodeDescriptor
|
import hep.dataforge.meta.descriptors.NodeDescriptor
|
||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.Name
|
||||||
import hep.dataforge.names.NameToken
|
import hep.dataforge.names.NameToken
|
||||||
@ -15,30 +15,35 @@ import kotlinx.serialization.encoding.Decoder
|
|||||||
import kotlinx.serialization.encoding.Encoder
|
import kotlinx.serialization.encoding.Encoder
|
||||||
|
|
||||||
public interface PrototypeHolder {
|
public interface PrototypeHolder {
|
||||||
public val parent: VisionGroup?
|
@VisionBuilder
|
||||||
public val prototypes: MutableVisionGroup?
|
public fun prototypes(builder: VisionContainerBuilder<Solid>.() -> Unit)
|
||||||
|
|
||||||
|
public fun getPrototype(name: Name): Solid?
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents 3-dimensional Visual Group
|
* Represents 3-dimensional Visual Group
|
||||||
|
* @param prototypes A container for templates visible inside this group
|
||||||
*/
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
@SerialName("group.solid")
|
@SerialName("group.solid")
|
||||||
public class SolidGroup : VisionGroupBase(), Solid, PrototypeHolder {
|
public class SolidGroup(
|
||||||
|
@Serializable(Prototypes.Companion::class) @SerialName("prototypes") private var prototypes: MutableVisionGroup? = null,
|
||||||
|
) : VisionGroupBase(), Solid, PrototypeHolder {
|
||||||
|
|
||||||
override val descriptor: NodeDescriptor get() = Solid.descriptor
|
override val descriptor: NodeDescriptor get() = Solid.descriptor
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A container for templates visible inside this group
|
* Ger a prototype redirecting the request to the parent if prototype is not found
|
||||||
*/
|
*/
|
||||||
@Serializable(Prototypes.Companion::class)
|
override fun getPrototype(name: Name): Solid? =
|
||||||
override var prototypes: MutableVisionGroup? = null
|
(prototypes?.get(name) as? Solid) ?: (parent as? PrototypeHolder)?.getPrototype(name)
|
||||||
private set
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create or edit prototype node as a group
|
* Create or edit prototype node as a group
|
||||||
*/
|
*/
|
||||||
public fun prototypes(builder: VisionContainerBuilder<Solid>.() -> Unit): Unit {
|
override fun prototypes(builder: VisionContainerBuilder<Solid>.() -> Unit): Unit {
|
||||||
(prototypes ?: Prototypes().also {
|
(prototypes ?: Prototypes().also {
|
||||||
prototypes = it
|
prototypes = it
|
||||||
it.parent = this
|
it.parent = this
|
||||||
@ -51,13 +56,6 @@ public class SolidGroup : VisionGroupBase(), Solid, PrototypeHolder {
|
|||||||
|
|
||||||
override var scale: Point3D? = null
|
override var scale: Point3D? = null
|
||||||
|
|
||||||
override fun attachChildren() {
|
|
||||||
prototypes?.parent = this
|
|
||||||
prototypes?.attachChildren()
|
|
||||||
super.attachChildren()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// /**
|
// /**
|
||||||
// * TODO add special static group to hold statics without propagation
|
// * TODO add special static group to hold statics without propagation
|
||||||
// */
|
// */
|
||||||
@ -80,19 +78,17 @@ public fun SolidGroup(block: SolidGroup.() -> Unit): SolidGroup {
|
|||||||
return SolidGroup().apply(block)
|
return SolidGroup().apply(block)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Ger a prototype redirecting the request to the parent if prototype is not found
|
|
||||||
*/
|
|
||||||
public tailrec fun PrototypeHolder.getPrototype(name: Name): Solid? =
|
|
||||||
prototypes?.get(name) as? Solid ?: (parent as? PrototypeHolder)?.getPrototype(name)
|
|
||||||
|
|
||||||
@VisionBuilder
|
@VisionBuilder
|
||||||
public fun VisionContainerBuilder<Vision>.group(name: Name = Name.EMPTY, action: SolidGroup.() -> Unit = {}): SolidGroup =
|
public fun VisionContainerBuilder<Vision>.group(
|
||||||
|
name: Name = Name.EMPTY,
|
||||||
|
action: SolidGroup.() -> Unit = {},
|
||||||
|
): SolidGroup =
|
||||||
SolidGroup().apply(action).also { set(name, it) }
|
SolidGroup().apply(action).also { set(name, it) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Define a group with given [name], attach it to this parent and return it.
|
* Define a group with given [name], attach it to this parent and return it.
|
||||||
*/
|
*/
|
||||||
|
@VisionBuilder
|
||||||
public fun VisionContainerBuilder<Vision>.group(name: String, action: SolidGroup.() -> Unit = {}): SolidGroup =
|
public fun VisionContainerBuilder<Vision>.group(name: String, action: SolidGroup.() -> Unit = {}): SolidGroup =
|
||||||
SolidGroup().apply(action).also { set(name, it) }
|
SolidGroup().apply(action).also { set(name, it) }
|
||||||
|
|
||||||
@ -102,27 +98,30 @@ public fun VisionContainerBuilder<Vision>.group(name: String, action: SolidGroup
|
|||||||
@Serializable(Prototypes.Companion::class)
|
@Serializable(Prototypes.Companion::class)
|
||||||
internal class Prototypes(
|
internal class Prototypes(
|
||||||
children: Map<NameToken, Vision> = emptyMap(),
|
children: Map<NameToken, Vision> = emptyMap(),
|
||||||
) : VisionGroupBase(), PrototypeHolder {
|
) : VisionGroupBase(children as? MutableMap<NameToken, Vision> ?: children.toMutableMap()), PrototypeHolder {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
this.childrenInternal.putAll(children)
|
//used during deserialization only
|
||||||
}
|
|
||||||
|
|
||||||
override var properties: Config?
|
|
||||||
get() = null
|
|
||||||
set(_) {
|
|
||||||
error("Can't define properties for prototypes block")
|
|
||||||
}
|
|
||||||
|
|
||||||
override val prototypes: MutableVisionGroup get() = this
|
|
||||||
|
|
||||||
override fun attachChildren() {
|
|
||||||
children.values.forEach {
|
children.values.forEach {
|
||||||
it.parent = parent
|
it.parent = parent
|
||||||
(it as? VisionGroup)?.attachChildren()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getOwnProperty(name: Name): MetaItem? = null
|
||||||
|
|
||||||
|
override fun getProperty(
|
||||||
|
name: Name,
|
||||||
|
inherit: Boolean,
|
||||||
|
includeStyles: Boolean,
|
||||||
|
includeDefaults: Boolean,
|
||||||
|
): MetaItem? = null
|
||||||
|
|
||||||
|
override fun setProperty(name: Name, item: MetaItem?, notify: Boolean) {
|
||||||
|
error("Can't ser property of prototypes container")
|
||||||
|
}
|
||||||
|
|
||||||
|
override val descriptor: NodeDescriptor? = null
|
||||||
|
|
||||||
companion object : KSerializer<MutableVisionGroup> {
|
companion object : KSerializer<MutableVisionGroup> {
|
||||||
|
|
||||||
private val mapSerializer: KSerializer<Map<NameToken, Vision>> =
|
private val mapSerializer: KSerializer<Map<NameToken, Vision>> =
|
||||||
@ -142,4 +141,10 @@ internal class Prototypes(
|
|||||||
mapSerializer.serialize(encoder, value.children)
|
mapSerializer.serialize(encoder, value.children)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun prototypes(builder: VisionContainerBuilder<Solid>.() -> Unit) {
|
||||||
|
apply(builder)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getPrototype(name: Name): Solid? = get(name) as? Solid
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,16 @@ import hep.dataforge.context.AbstractPlugin
|
|||||||
import hep.dataforge.context.Context
|
import hep.dataforge.context.Context
|
||||||
import hep.dataforge.context.PluginFactory
|
import hep.dataforge.context.PluginFactory
|
||||||
import hep.dataforge.context.PluginTag
|
import hep.dataforge.context.PluginTag
|
||||||
|
import hep.dataforge.meta.DFExperimental
|
||||||
import hep.dataforge.meta.Meta
|
import hep.dataforge.meta.Meta
|
||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.Name
|
||||||
import hep.dataforge.names.toName
|
import hep.dataforge.names.toName
|
||||||
import hep.dataforge.vision.*
|
import hep.dataforge.vision.Vision
|
||||||
|
import hep.dataforge.vision.VisionBase
|
||||||
|
import hep.dataforge.vision.VisionGroupBase
|
||||||
|
import hep.dataforge.vision.VisionManager
|
||||||
import hep.dataforge.vision.VisionManager.Companion.VISION_SERIALIZER_MODULE_TARGET
|
import hep.dataforge.vision.VisionManager.Companion.VISION_SERIALIZER_MODULE_TARGET
|
||||||
|
import hep.dataforge.vision.html.VisionOutput
|
||||||
import kotlinx.serialization.PolymorphicSerializer
|
import kotlinx.serialization.PolymorphicSerializer
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.modules.PolymorphicModuleBuilder
|
import kotlinx.serialization.modules.PolymorphicModuleBuilder
|
||||||
@ -30,13 +35,13 @@ public class SolidManager(meta: Meta) : AbstractPlugin(meta) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public companion object : PluginFactory<SolidManager> {
|
public companion object : PluginFactory<SolidManager> {
|
||||||
override val tag: PluginTag = PluginTag(name = "visual.spatial", group = PluginTag.DATAFORGE_GROUP)
|
override val tag: PluginTag = PluginTag(name = "vision.solid", group = PluginTag.DATAFORGE_GROUP)
|
||||||
override val type: KClass<out SolidManager> = SolidManager::class
|
override val type: KClass<out SolidManager> = SolidManager::class
|
||||||
override fun invoke(meta: Meta, context: Context): SolidManager = SolidManager(meta)
|
override fun invoke(meta: Meta, context: Context): SolidManager = SolidManager(meta)
|
||||||
|
|
||||||
private fun PolymorphicModuleBuilder<Solid>.solids() {
|
private fun PolymorphicModuleBuilder<Solid>.solids() {
|
||||||
subclass(SolidGroup.serializer())
|
subclass(SolidGroup.serializer())
|
||||||
subclass(SolidReference.serializer())
|
subclass(SolidReferenceGroup.serializer())
|
||||||
subclass(Composite.serializer())
|
subclass(Composite.serializer())
|
||||||
subclass(Tube.serializer())
|
subclass(Tube.serializer())
|
||||||
subclass(Box.serializer())
|
subclass(Box.serializer())
|
||||||
@ -67,10 +72,9 @@ public class SolidManager(meta: Meta) : AbstractPlugin(meta) {
|
|||||||
|
|
||||||
public fun encodeToString(solid: Solid): String = jsonForSolids.encodeToString(PolymorphicSerializer(Vision::class), solid)
|
public fun encodeToString(solid: Solid): String = jsonForSolids.encodeToString(PolymorphicSerializer(Vision::class), solid)
|
||||||
|
|
||||||
fun decodeFromString(str: String): Solid = jsonForSolids.decodeFromString(PolymorphicSerializer(Solid::class), str).also {
|
public fun decodeFromString(str: String): Solid = jsonForSolids.decodeFromString(PolymorphicSerializer(Solid::class), str)
|
||||||
if(it is VisionGroup){
|
|
||||||
it.attachChildren()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@DFExperimental
|
||||||
|
public inline fun VisionOutput.solid(block: SolidGroup.() -> Unit): SolidGroup = SolidGroup().apply(block)
|
@ -6,53 +6,12 @@ import hep.dataforge.meta.descriptors.attributes
|
|||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.Name
|
||||||
import hep.dataforge.names.asName
|
import hep.dataforge.names.asName
|
||||||
import hep.dataforge.names.plus
|
import hep.dataforge.names.plus
|
||||||
import hep.dataforge.values.Value
|
|
||||||
import hep.dataforge.values.ValueType
|
import hep.dataforge.values.ValueType
|
||||||
import hep.dataforge.values.asValue
|
import hep.dataforge.values.asValue
|
||||||
import hep.dataforge.values.string
|
import hep.dataforge.vision.*
|
||||||
import hep.dataforge.vision.Colors
|
|
||||||
import hep.dataforge.vision.VisionBuilder
|
|
||||||
import hep.dataforge.vision.setProperty
|
|
||||||
import hep.dataforge.vision.solid.SolidMaterial.Companion.MATERIAL_COLOR_KEY
|
import hep.dataforge.vision.solid.SolidMaterial.Companion.MATERIAL_COLOR_KEY
|
||||||
import hep.dataforge.vision.solid.SolidMaterial.Companion.MATERIAL_KEY
|
import hep.dataforge.vision.solid.SolidMaterial.Companion.MATERIAL_KEY
|
||||||
import hep.dataforge.vision.solid.SolidMaterial.Companion.MATERIAL_OPACITY_KEY
|
import hep.dataforge.vision.solid.SolidMaterial.Companion.MATERIAL_OPACITY_KEY
|
||||||
import hep.dataforge.vision.widgetType
|
|
||||||
|
|
||||||
@VisionBuilder
|
|
||||||
public class ColorAccessor(private val parent: MutableItemProvider, private val colorKey: Name) {
|
|
||||||
public var value: Value?
|
|
||||||
get() = parent.getItem(colorKey).value
|
|
||||||
set(value) {
|
|
||||||
parent[colorKey] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public var ColorAccessor?.string: String?
|
|
||||||
get() = this?.value?.string
|
|
||||||
set(value) {
|
|
||||||
this?.value = value?.asValue()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set [webcolor](https://en.wikipedia.org/wiki/Web_colors) as string
|
|
||||||
*/
|
|
||||||
public operator fun ColorAccessor?.invoke(webColor: String) {
|
|
||||||
this?.value = webColor.asValue()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set color as RGB integer
|
|
||||||
*/
|
|
||||||
public operator fun ColorAccessor?.invoke(rgb: Int) {
|
|
||||||
this?.value = rgb.asValue()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set color as RGB
|
|
||||||
*/
|
|
||||||
public operator fun ColorAccessor?.invoke(r: UByte, g: UByte, b: UByte) {
|
|
||||||
this?.value = Colors.rgbToString(r, g, b).asValue()
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisionBuilder
|
@VisionBuilder
|
||||||
public class SolidMaterial : Scheme() {
|
public class SolidMaterial : Scheme() {
|
||||||
@ -60,12 +19,12 @@ public class SolidMaterial : Scheme() {
|
|||||||
/**
|
/**
|
||||||
* Primary web-color for the material
|
* Primary web-color for the material
|
||||||
*/
|
*/
|
||||||
public var color: ColorAccessor = ColorAccessor(config, COLOR_KEY)
|
public val color: ColorAccessor = ColorAccessor(this, COLOR_KEY)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specular color for phong material
|
* Specular color for phong material
|
||||||
*/
|
*/
|
||||||
public var specularColor: ColorAccessor = ColorAccessor(config, SPECULAR_COLOR_KEY)
|
public val specularColor: ColorAccessor = ColorAccessor(this, SPECULAR_COLOR_KEY)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opacity
|
* Opacity
|
||||||
@ -92,11 +51,27 @@ public class SolidMaterial : Scheme() {
|
|||||||
public override val descriptor: NodeDescriptor by lazy {
|
public override val descriptor: NodeDescriptor by lazy {
|
||||||
//must be lazy to avoid initialization bug
|
//must be lazy to avoid initialization bug
|
||||||
NodeDescriptor {
|
NodeDescriptor {
|
||||||
|
inherited = true
|
||||||
|
usesStyles = true
|
||||||
|
|
||||||
value(COLOR_KEY) {
|
value(COLOR_KEY) {
|
||||||
|
inherited = true
|
||||||
|
usesStyles = true
|
||||||
type(ValueType.STRING, ValueType.NUMBER)
|
type(ValueType.STRING, ValueType.NUMBER)
|
||||||
widgetType = "color"
|
widgetType = "color"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
value(SPECULAR_COLOR_KEY) {
|
||||||
|
inherited = true
|
||||||
|
usesStyles = true
|
||||||
|
type(ValueType.STRING, ValueType.NUMBER)
|
||||||
|
widgetType = "color"
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
|
||||||
value(OPACITY_KEY) {
|
value(OPACITY_KEY) {
|
||||||
|
inherited = true
|
||||||
|
usesStyles = true
|
||||||
type(ValueType.NUMBER)
|
type(ValueType.NUMBER)
|
||||||
default(1.0)
|
default(1.0)
|
||||||
attributes {
|
attributes {
|
||||||
@ -107,6 +82,8 @@ public class SolidMaterial : Scheme() {
|
|||||||
widgetType = "slider"
|
widgetType = "slider"
|
||||||
}
|
}
|
||||||
value(WIREFRAME_KEY) {
|
value(WIREFRAME_KEY) {
|
||||||
|
inherited = true
|
||||||
|
usesStyles = true
|
||||||
type(ValueType.BOOLEAN)
|
type(ValueType.BOOLEAN)
|
||||||
default(false)
|
default(false)
|
||||||
}
|
}
|
||||||
@ -115,24 +92,23 @@ public class SolidMaterial : Scheme() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public val Solid.color: ColorAccessor get() = ColorAccessor(config, MATERIAL_COLOR_KEY)
|
public val Solid.color: ColorAccessor
|
||||||
|
get() = ColorAccessor(
|
||||||
|
allProperties(inherit = true),
|
||||||
|
MATERIAL_COLOR_KEY
|
||||||
|
)
|
||||||
|
|
||||||
public var Solid.material: SolidMaterial?
|
public var Solid.material: SolidMaterial?
|
||||||
get() = getProperty(MATERIAL_KEY).node?.let { SolidMaterial.read(it) }
|
get() = getProperty(MATERIAL_KEY, inherit = true).node?.let { SolidMaterial.read(it) }
|
||||||
set(value) = setProperty(MATERIAL_KEY, value?.config)
|
set(value) = setProperty(MATERIAL_KEY, value?.rootNode)
|
||||||
|
|
||||||
@VisionBuilder
|
@VisionBuilder
|
||||||
public fun Solid.material(builder: SolidMaterial.() -> Unit) {
|
public fun Solid.material(builder: SolidMaterial.() -> Unit) {
|
||||||
val node = config[MATERIAL_KEY].node
|
ownProperties.getChild(MATERIAL_KEY).update(SolidMaterial, builder)
|
||||||
if (node != null) {
|
|
||||||
SolidMaterial.update(node, builder)
|
|
||||||
} else {
|
|
||||||
config[MATERIAL_KEY] = SolidMaterial(builder)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public var Solid.opacity: Number?
|
public var Solid.opacity: Number?
|
||||||
get() = getProperty(MATERIAL_OPACITY_KEY).number
|
get() = getProperty(MATERIAL_OPACITY_KEY, inherit = true).number
|
||||||
set(value) {
|
set(value) {
|
||||||
setProperty(MATERIAL_OPACITY_KEY, value?.asValue())
|
setProperty(MATERIAL_OPACITY_KEY, value?.asValue())
|
||||||
}
|
}
|
@ -3,43 +3,32 @@ package hep.dataforge.vision.solid
|
|||||||
import hep.dataforge.meta.*
|
import hep.dataforge.meta.*
|
||||||
import hep.dataforge.meta.descriptors.NodeDescriptor
|
import hep.dataforge.meta.descriptors.NodeDescriptor
|
||||||
import hep.dataforge.names.*
|
import hep.dataforge.names.*
|
||||||
|
import hep.dataforge.values.Null
|
||||||
import hep.dataforge.vision.*
|
import hep.dataforge.vision.*
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.Transient
|
|
||||||
import kotlin.collections.set
|
|
||||||
|
|
||||||
public abstract class AbstractReference : SolidBase(), VisionGroup {
|
public interface SolidReference : Vision {
|
||||||
public abstract val prototype: Solid
|
public val prototype: Solid
|
||||||
|
}
|
||||||
|
|
||||||
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? = sequence {
|
private fun SolidReference.getRefProperty(
|
||||||
yield(properties?.get(name))
|
name: Name,
|
||||||
yieldAll(getStyleItems(name))
|
inherit: Boolean,
|
||||||
yield(prototype.getProperty(name))
|
includeStyles: Boolean,
|
||||||
|
includeDefaults: Boolean,
|
||||||
|
): MetaItem? {
|
||||||
|
return sequence {
|
||||||
|
yield(getOwnProperty(name))
|
||||||
|
if (includeStyles) {
|
||||||
|
yieldAll(getStyleItems(name))
|
||||||
|
}
|
||||||
|
yield(prototype.getProperty(name, inherit, includeStyles, includeDefaults))
|
||||||
if (inherit) {
|
if (inherit) {
|
||||||
yield(parent?.getProperty(name, inherit))
|
yield(parent?.getProperty(name, inherit))
|
||||||
}
|
}
|
||||||
}.merge()
|
}.merge()
|
||||||
|
|
||||||
override var styles: List<String>
|
|
||||||
get() = (properties[Vision.STYLE_KEY]?.stringList ?: emptyList()) + prototype.styles
|
|
||||||
set(value) {
|
|
||||||
config[Vision.STYLE_KEY] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
override val allProperties: Laminate
|
|
||||||
get() = Laminate(
|
|
||||||
properties,
|
|
||||||
allStyles,
|
|
||||||
prototype.allProperties,
|
|
||||||
parent?.allProperties,
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun attachChildren() {
|
|
||||||
//do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
override val descriptor: NodeDescriptor get() = prototype.descriptor
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -47,9 +36,9 @@ public abstract class AbstractReference : SolidBase(), VisionGroup {
|
|||||||
*/
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
@SerialName("solid.ref")
|
@SerialName("solid.ref")
|
||||||
public class SolidReference(
|
public class SolidReferenceGroup(
|
||||||
public val templateName: Name,
|
public val templateName: Name,
|
||||||
) : AbstractReference(), Solid {
|
) : SolidBase(), SolidReference, VisionGroup {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recursively search for defined template in the parent
|
* Recursively search for defined template in the parent
|
||||||
@ -58,59 +47,102 @@ public class SolidReference(
|
|||||||
get() = (parent as? SolidGroup)?.getPrototype(templateName)
|
get() = (parent as? SolidGroup)?.getPrototype(templateName)
|
||||||
?: error("Prototype with name $templateName not found in $parent")
|
?: error("Prototype with name $templateName not found in $parent")
|
||||||
|
|
||||||
@Transient
|
override val children: Map<NameToken, Vision>
|
||||||
private val propertyCache: HashMap<Name, Config> = HashMap()
|
|
||||||
|
|
||||||
|
|
||||||
override val children: Map<NameToken, SolidReference.ReferenceChild>
|
|
||||||
get() = (prototype as? VisionGroup)?.children
|
get() = (prototype as? VisionGroup)?.children
|
||||||
?.filter { !it.key.toString().startsWith("@") }
|
?.filter { !it.key.toString().startsWith("@") }
|
||||||
?.mapValues {
|
?.mapValues {
|
||||||
ReferenceChild(it.key.asName())
|
ReferenceChild(it.key.asName())
|
||||||
} ?: emptyMap()
|
} ?: emptyMap()
|
||||||
|
|
||||||
private fun childPropertyName(childName: Name, propertyName: Name): Name {
|
private fun childToken(childName: Name): NameToken =
|
||||||
return NameToken(REFERENCE_CHILD_PROPERTY_PREFIX, childName.toString()) + propertyName
|
NameToken(REFERENCE_CHILD_PROPERTY_PREFIX, childName.toString())
|
||||||
|
|
||||||
|
private fun childPropertyName(childName: Name, propertyName: Name): Name =
|
||||||
|
childToken(childName) + propertyName
|
||||||
|
|
||||||
|
private fun getChildProperty(childName: Name, propertyName: Name): MetaItem? {
|
||||||
|
return getOwnProperty(childPropertyName(childName, propertyName))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setChildProperty(childName: Name, propertyName: Name, item: MetaItem?, notify: Boolean) {
|
||||||
|
setProperty(childPropertyName(childName, propertyName), item, notify)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun prototypeFor(name: Name): Solid {
|
private fun prototypeFor(name: Name): Solid {
|
||||||
return (prototype as? SolidGroup)?.get(name) as? Solid
|
return if (name.isEmpty()) prototype else {
|
||||||
?: error("Prototype with name $name not found in $this")
|
(prototype as? SolidGroup)?.get(name) as? Solid
|
||||||
|
?: error("Prototype with name $name not found in $this")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//override fun findAllStyles(): Laminate = Laminate((styles + prototype.styles).mapNotNull { findStyle(it) })
|
override fun getProperty(
|
||||||
|
name: Name,
|
||||||
|
inherit: Boolean,
|
||||||
|
includeStyles: Boolean,
|
||||||
|
includeDefaults: Boolean,
|
||||||
|
): MetaItem? = getRefProperty(name, inherit, includeStyles, includeDefaults)
|
||||||
|
|
||||||
|
override val descriptor: NodeDescriptor get() = prototype.descriptor
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A ProxyChild is created temporarily only to interact with properties, it does not store any values
|
* A ProxyChild is created temporarily only to interact with properties, it does not store any values
|
||||||
* (properties are stored in external cache) and created and destroyed on-demand).
|
* (properties are stored in external cache) and created and destroyed on-demand).
|
||||||
*/
|
*/
|
||||||
public inner class ReferenceChild(public val name: Name) : AbstractReference() {
|
private inner class ReferenceChild(private val childName: Name) : SolidReference, VisionGroup {
|
||||||
|
|
||||||
override val prototype: Solid get() = prototypeFor(name)
|
override val prototype: Solid get() = prototypeFor(childName)
|
||||||
|
|
||||||
override val children: Map<NameToken, Vision>
|
override val children: Map<NameToken, Vision>
|
||||||
get() = (prototype as? VisionGroup)?.children
|
get() = (prototype as? VisionGroup)?.children
|
||||||
?.filter { !it.key.toString().startsWith("@") }
|
?.filter { !it.key.toString().startsWith("@") }
|
||||||
?.mapValues { (key, _) ->
|
?.mapValues { (key, _) ->
|
||||||
ReferenceChild(name + key.asName())
|
ReferenceChild(childName + key.asName())
|
||||||
} ?: emptyMap()
|
} ?: emptyMap()
|
||||||
|
|
||||||
override var properties: Config?
|
override val meta: Meta get() = TODO()// getChildProperty(childName, Name.EMPTY).node ?: Meta.EMPTY
|
||||||
get() = propertyCache[name]
|
|
||||||
|
override fun getOwnProperty(name: Name): MetaItem? = getChildProperty(childName, name)
|
||||||
|
|
||||||
|
override fun setProperty(name: Name, item: MetaItem?, notify: Boolean) {
|
||||||
|
setChildProperty(childName, name, item, notify)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getProperty(
|
||||||
|
name: Name,
|
||||||
|
inherit: Boolean,
|
||||||
|
includeStyles: Boolean,
|
||||||
|
includeDefaults: Boolean,
|
||||||
|
): MetaItem? = getRefProperty(name, inherit, includeStyles, includeDefaults)
|
||||||
|
|
||||||
|
override var parent: VisionGroup?
|
||||||
|
get() {
|
||||||
|
val parentName = childName.cutLast()
|
||||||
|
return if (parentName.isEmpty()) this@SolidReferenceGroup else ReferenceChild(parentName)
|
||||||
|
}
|
||||||
set(value) {
|
set(value) {
|
||||||
if (value == null) {
|
error("Setting a parent for a reference child is not possible")
|
||||||
propertyCache.remove(name)?.also {
|
}
|
||||||
//Removing listener if it is present
|
|
||||||
removeChangeListener(this@SolidReference)
|
override fun onPropertyChange(scope: CoroutineScope, callback: suspend (Name) -> Unit) {
|
||||||
}
|
this@SolidReferenceGroup.onPropertyChange(scope) { name ->
|
||||||
} else {
|
if (name.startsWith(childToken(childName))) {
|
||||||
propertyCache[name] = value.also {
|
callback(name.cutFirst())
|
||||||
onPropertyChange(this@SolidReference) { propertyName ->
|
|
||||||
this@SolidReference.propertyChanged(childPropertyName(name, propertyName))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun notifyPropertyChanged(propertyName: Name) {
|
||||||
|
this@SolidReferenceGroup.notifyPropertyChanged(childPropertyName(childName, propertyName))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun update(change: VisionChange) {
|
||||||
|
change.properties?.let {
|
||||||
|
updateProperties(Name.EMPTY, it.asMetaItem())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val descriptor: NodeDescriptor get() = prototype.descriptor
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,13 +152,10 @@ public class SolidReference(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a vision prototype if it is a [SolidReference] or vision itself if it is not
|
* Get a vision prototype if it is a [SolidReferenceGroup] or vision itself if it is not
|
||||||
*/
|
*/
|
||||||
public val Vision.prototype: Vision
|
public val Vision.prototype: Vision
|
||||||
get() = when (this) {
|
get() = if (this is SolidReference) prototype else this
|
||||||
is AbstractReference -> prototype
|
|
||||||
else -> this
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create ref for existing prototype
|
* Create ref for existing prototype
|
||||||
@ -134,16 +163,16 @@ public val Vision.prototype: Vision
|
|||||||
public fun SolidGroup.ref(
|
public fun SolidGroup.ref(
|
||||||
templateName: Name,
|
templateName: Name,
|
||||||
name: String = "",
|
name: String = "",
|
||||||
): SolidReference = SolidReference(templateName).also { set(name, it) }
|
): SolidReferenceGroup = SolidReferenceGroup(templateName).also { set(name, it) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add new [SolidReference] wrapping given object and automatically adding it to the prototypes
|
* Add new [SolidReferenceGroup] wrapping given object and automatically adding it to the prototypes
|
||||||
*/
|
*/
|
||||||
public fun SolidGroup.ref(
|
public fun SolidGroup.ref(
|
||||||
name: String,
|
name: String,
|
||||||
obj: Solid,
|
obj: Solid,
|
||||||
templateName: Name = name.toName(),
|
templateName: Name = name.toName(),
|
||||||
): SolidReference {
|
): SolidReferenceGroup {
|
||||||
val existing = getPrototype(templateName)
|
val existing = getPrototype(templateName)
|
||||||
if (existing == null) {
|
if (existing == null) {
|
||||||
prototypes {
|
prototypes {
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
package hep.dataforge.vision.solid.specifications
|
package hep.dataforge.vision.solid.specifications
|
||||||
|
|
||||||
import hep.dataforge.meta.*
|
import hep.dataforge.meta.Scheme
|
||||||
|
import hep.dataforge.meta.SchemeSpec
|
||||||
|
import hep.dataforge.meta.boolean
|
||||||
|
import hep.dataforge.meta.double
|
||||||
|
|
||||||
public class Axes : Scheme() {
|
public class Axes : Scheme() {
|
||||||
public var visible: Boolean by boolean(!config.isEmpty())
|
public var visible: Boolean by boolean(false)
|
||||||
public var size: Double by double(AXIS_SIZE)
|
public var size: Double by double(AXIS_SIZE)
|
||||||
public var width: Double by double(AXIS_WIDTH)
|
public var width: Double by double(AXIS_WIDTH)
|
||||||
|
|
||||||
|
@ -4,9 +4,10 @@ import hep.dataforge.meta.*
|
|||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.Name
|
||||||
|
|
||||||
public class Canvas3DOptions : Scheme() {
|
public class Canvas3DOptions : Scheme() {
|
||||||
public var axes: Axes by spec(Axes, Axes.empty())
|
public var axes: Axes by spec(Axes)
|
||||||
public var camera: Camera by spec(Camera, Camera.empty())
|
public var light: Light by spec(Light)
|
||||||
public var controls: Controls by spec(Controls, Controls.empty())
|
public var camera: Camera by spec(Camera)
|
||||||
|
public var controls: Controls by spec(Controls)
|
||||||
|
|
||||||
public var minSize: Int by int(400)
|
public var minSize: Int by int(400)
|
||||||
public var minWith: Number by number { minSize }
|
public var minWith: Number by number { minSize }
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
package hep.dataforge.vision.solid.specifications
|
||||||
|
|
||||||
|
import hep.dataforge.meta.Scheme
|
||||||
|
import hep.dataforge.meta.SchemeSpec
|
||||||
|
|
||||||
|
public class Light : Scheme() {
|
||||||
|
public companion object : SchemeSpec<Light>(::Light)
|
||||||
|
}
|
@ -1,18 +1,15 @@
|
|||||||
package hep.dataforge.vision.solid.transform
|
package hep.dataforge.vision.solid.transform
|
||||||
|
|
||||||
import hep.dataforge.meta.DFExperimental
|
import hep.dataforge.meta.DFExperimental
|
||||||
import hep.dataforge.meta.update
|
|
||||||
import hep.dataforge.names.asName
|
import hep.dataforge.names.asName
|
||||||
import hep.dataforge.vision.MutableVisionGroup
|
import hep.dataforge.vision.*
|
||||||
import hep.dataforge.vision.Vision
|
|
||||||
import hep.dataforge.vision.VisionGroup
|
|
||||||
import hep.dataforge.vision.solid.*
|
import hep.dataforge.vision.solid.*
|
||||||
|
|
||||||
@DFExperimental
|
@DFExperimental
|
||||||
internal fun mergeChild(parent: VisionGroup, child: Vision): Vision {
|
internal fun mergeChild(parent: VisionGroup, child: Vision): Vision {
|
||||||
return child.apply {
|
return child.apply {
|
||||||
|
|
||||||
config.update(parent.config)
|
configure(parent.meta)
|
||||||
|
|
||||||
//parent.properties?.let { config.update(it) }
|
//parent.properties?.let { config.update(it) }
|
||||||
|
|
||||||
@ -40,7 +37,7 @@ internal object RemoveSingleChild : VisualTreeTransform<SolidGroup>() {
|
|||||||
override fun SolidGroup.transformInPlace() {
|
override fun SolidGroup.transformInPlace() {
|
||||||
fun MutableVisionGroup.replaceChildren() {
|
fun MutableVisionGroup.replaceChildren() {
|
||||||
children.forEach { (childName, parent) ->
|
children.forEach { (childName, parent) ->
|
||||||
if (parent is SolidReference) return@forEach //ignore refs
|
if (parent is SolidReferenceGroup) return@forEach //ignore refs
|
||||||
if (parent is MutableVisionGroup) {
|
if (parent is MutableVisionGroup) {
|
||||||
parent.replaceChildren()
|
parent.replaceChildren()
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import hep.dataforge.names.asName
|
|||||||
import hep.dataforge.vision.MutableVisionGroup
|
import hep.dataforge.vision.MutableVisionGroup
|
||||||
import hep.dataforge.vision.VisionGroup
|
import hep.dataforge.vision.VisionGroup
|
||||||
import hep.dataforge.vision.solid.SolidGroup
|
import hep.dataforge.vision.solid.SolidGroup
|
||||||
import hep.dataforge.vision.solid.SolidReference
|
import hep.dataforge.vision.solid.SolidReferenceGroup
|
||||||
|
|
||||||
@DFExperimental
|
@DFExperimental
|
||||||
internal object UnRef : VisualTreeTransform<SolidGroup>() {
|
internal object UnRef : VisualTreeTransform<SolidGroup>() {
|
||||||
@ -17,7 +17,7 @@ internal object UnRef : VisualTreeTransform<SolidGroup>() {
|
|||||||
counter.forEach { (key, value) ->
|
counter.forEach { (key, value) ->
|
||||||
reducer[key] = (reducer[key] ?: 0) + value
|
reducer[key] = (reducer[key] ?: 0) + value
|
||||||
}
|
}
|
||||||
} else if (obj is SolidReference) {
|
} else if (obj is SolidReferenceGroup) {
|
||||||
reducer[obj.templateName] = (reducer[obj.templateName] ?: 0) + 1
|
reducer[obj.templateName] = (reducer[obj.templateName] ?: 0) + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,9 +26,11 @@ internal object UnRef : VisualTreeTransform<SolidGroup>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun MutableVisionGroup.unref(name: Name) {
|
private fun MutableVisionGroup.unref(name: Name) {
|
||||||
(this as? SolidGroup)?.prototypes?.set(name, null)
|
(this as? SolidGroup)?.prototypes{
|
||||||
children.filter { (it.value as? SolidReference)?.templateName == name }.forEach { (key, value) ->
|
set(name, null)
|
||||||
val reference = value as SolidReference
|
}
|
||||||
|
children.filter { (it.value as? SolidReferenceGroup)?.templateName == name }.forEach { (key, value) ->
|
||||||
|
val reference = value as SolidReferenceGroup
|
||||||
val newChild = mergeChild(reference, reference.prototype)
|
val newChild = mergeChild(reference, reference.prototype)
|
||||||
newChild.parent = null
|
newChild.parent = null
|
||||||
set(key.asName(), newChild) // replace proxy with merged object
|
set(key.asName(), newChild) // replace proxy with merged object
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
package hep.dataforge.vision.solid
|
||||||
|
|
||||||
|
import hep.dataforge.vision.Colors
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class CompositeTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testCompositeBuilder(){
|
||||||
|
lateinit var composite: Composite
|
||||||
|
SolidGroup {
|
||||||
|
composite = composite(CompositeType.INTERSECT) {
|
||||||
|
y = 300
|
||||||
|
box(100, 100, 100) {
|
||||||
|
z = 50
|
||||||
|
}
|
||||||
|
sphere(50) {
|
||||||
|
detail = 32
|
||||||
|
}
|
||||||
|
material {
|
||||||
|
color("pink")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals("pink", composite.color.string)
|
||||||
|
}
|
||||||
|
}
|
@ -28,7 +28,7 @@ class ConvexTest {
|
|||||||
val json = SolidManager.jsonForSolids.encodeToJsonElement(Convex.serializer(), convex)
|
val json = SolidManager.jsonForSolids.encodeToJsonElement(Convex.serializer(), convex)
|
||||||
val meta = json.toMetaItem().node!!
|
val meta = json.toMetaItem().node!!
|
||||||
|
|
||||||
val points = meta.getIndexed("points").values.map { (it as MetaItem.NodeItem<*>).node.point3D() }
|
val points = meta.getIndexed("points").values.map { (it as NodeItem<*>).node.point3D() }
|
||||||
assertEquals(8, points.count())
|
assertEquals(8, points.count())
|
||||||
|
|
||||||
assertEquals(8, convex.points.size)
|
assertEquals(8, convex.points.size)
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
package hep.dataforge.vision.solid
|
package hep.dataforge.vision.solid
|
||||||
|
|
||||||
import hep.dataforge.meta.int
|
import hep.dataforge.meta.int
|
||||||
import hep.dataforge.meta.set
|
|
||||||
import hep.dataforge.names.asName
|
import hep.dataforge.names.asName
|
||||||
import hep.dataforge.vision.styleSheet
|
import hep.dataforge.vision.*
|
||||||
import hep.dataforge.vision.useStyle
|
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
@ -14,12 +12,12 @@ class PropertyTest {
|
|||||||
fun testInheritedProperty() {
|
fun testInheritedProperty() {
|
||||||
var box: Box? = null
|
var box: Box? = null
|
||||||
val group = SolidGroup().apply {
|
val group = SolidGroup().apply {
|
||||||
config["test"] = 22
|
setProperty("test", 22)
|
||||||
group {
|
group {
|
||||||
box = box(100, 100, 100)
|
box = box(100, 100, 100)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assertEquals(22, box?.getProperty("test".asName()).int)
|
assertEquals(22, box?.getProperty("test", inherit = true).int)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -37,7 +35,7 @@ class PropertyTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assertEquals(22, box?.getProperty("test".asName()).int)
|
assertEquals(22, box?.getProperty("test").int)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -60,7 +58,7 @@ class PropertyTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testReferenceStyleProperty() {
|
fun testReferenceStyleProperty() {
|
||||||
var box: SolidReference? = null
|
var box: SolidReferenceGroup? = null
|
||||||
val group = SolidGroup{
|
val group = SolidGroup{
|
||||||
styleSheet {
|
styleSheet {
|
||||||
set("testStyle") {
|
set("testStyle") {
|
||||||
|
@ -15,7 +15,7 @@ fun SolidGroup.refGroup(
|
|||||||
name: String,
|
name: String,
|
||||||
templateName: Name = name.toName(),
|
templateName: Name = name.toName(),
|
||||||
block: MutableVisionGroup.() -> Unit
|
block: MutableVisionGroup.() -> Unit
|
||||||
): SolidReference {
|
): SolidReferenceGroup {
|
||||||
val group = SolidGroup().apply(block)
|
val group = SolidGroup().apply(block)
|
||||||
return ref(name, group, templateName)
|
return ref(name, group, templateName)
|
||||||
}
|
}
|
||||||
@ -32,7 +32,7 @@ class SerializationTest {
|
|||||||
val string = SolidManager.encodeToString(cube)
|
val string = SolidManager.encodeToString(cube)
|
||||||
println(string)
|
println(string)
|
||||||
val newCube = SolidManager.decodeFromString(string)
|
val newCube = SolidManager.decodeFromString(string)
|
||||||
assertEquals(cube.config, newCube.config)
|
assertEquals(cube.meta, newCube.meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -53,7 +53,7 @@ class SerializationTest {
|
|||||||
val string = SolidManager.encodeToString(group)
|
val string = SolidManager.encodeToString(group)
|
||||||
println(string)
|
println(string)
|
||||||
val reconstructed = SolidManager.decodeFromString(string) as SolidGroup
|
val reconstructed = SolidManager.decodeFromString(string) as SolidGroup
|
||||||
assertEquals(group["cube"]?.config, reconstructed["cube"]?.config)
|
assertEquals(group["cube"]?.meta, reconstructed["cube"]?.meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
package hep.dataforge.vision.solid
|
||||||
|
|
||||||
|
import hep.dataforge.vision.get
|
||||||
|
import hep.dataforge.vision.style
|
||||||
|
import hep.dataforge.vision.styles
|
||||||
|
import hep.dataforge.vision.useStyle
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class SolidReferenceTest {
|
||||||
|
val groupWithReference = SolidGroup {
|
||||||
|
val referenceStyle by style {
|
||||||
|
SolidMaterial.MATERIAL_COLOR_KEY put "red"
|
||||||
|
}
|
||||||
|
ref("test", Box(100f,100f,100f).apply {
|
||||||
|
color("blue")
|
||||||
|
useStyle(referenceStyle)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testReferenceProperty(){
|
||||||
|
assertEquals("blue", (groupWithReference["test"] as Solid).color.string)
|
||||||
|
}
|
||||||
|
}
|
@ -29,7 +29,7 @@ class VisionUpdateTest {
|
|||||||
targetVision.update(dif)
|
targetVision.update(dif)
|
||||||
assertTrue { targetVision["top"] is SolidGroup }
|
assertTrue { targetVision["top"] is SolidGroup }
|
||||||
assertEquals("red", (targetVision["origin"] as Solid).color.string) // Should work
|
assertEquals("red", (targetVision["origin"] as Solid).color.string) // Should work
|
||||||
assertEquals("#00007b", (targetVision["top"] as SolidGroup).color.string) // new item always takes precedence
|
assertEquals("#00007b", (targetVision["top"] as Solid).color.string) // new item always takes precedence
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -45,7 +45,7 @@ class VisionUpdateTest {
|
|||||||
val serialized = visionManager.jsonFormat.encodeToString(VisionChange.serializer(), change)
|
val serialized = visionManager.jsonFormat.encodeToString(VisionChange.serializer(), change)
|
||||||
println(serialized)
|
println(serialized)
|
||||||
val reconstructed = visionManager.jsonFormat.decodeFromString(VisionChange.serializer(), serialized)
|
val reconstructed = visionManager.jsonFormat.decodeFromString(VisionChange.serializer(), serialized)
|
||||||
assertEquals(change.propertyChange,reconstructed.propertyChange)
|
assertEquals(change.properties,reconstructed.properties)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -9,10 +9,8 @@ import hep.dataforge.names.startsWith
|
|||||||
import hep.dataforge.vision.solid.Solid
|
import hep.dataforge.vision.solid.Solid
|
||||||
import hep.dataforge.vision.solid.SolidMaterial
|
import hep.dataforge.vision.solid.SolidMaterial
|
||||||
import hep.dataforge.vision.solid.layer
|
import hep.dataforge.vision.solid.layer
|
||||||
import hep.dataforge.vision.solid.three.ThreeMaterials.getMaterial
|
|
||||||
import info.laht.threekt.core.BufferGeometry
|
import info.laht.threekt.core.BufferGeometry
|
||||||
import info.laht.threekt.geometries.EdgesGeometry
|
import info.laht.threekt.geometries.EdgesGeometry
|
||||||
import info.laht.threekt.geometries.WireframeGeometry
|
|
||||||
import info.laht.threekt.objects.LineSegments
|
import info.laht.threekt.objects.LineSegments
|
||||||
import info.laht.threekt.objects.Mesh
|
import info.laht.threekt.objects.Mesh
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
@ -21,14 +19,14 @@ import kotlin.reflect.KClass
|
|||||||
* Basic geometry-based factory
|
* Basic geometry-based factory
|
||||||
*/
|
*/
|
||||||
public abstract class MeshThreeFactory<in T : Solid>(
|
public abstract class MeshThreeFactory<in T : Solid>(
|
||||||
override val type: KClass<in T>
|
override val type: KClass<in T>,
|
||||||
) : ThreeFactory<T> {
|
) : ThreeFactory<T> {
|
||||||
/**
|
/**
|
||||||
* Build a geometry for an object
|
* Build a geometry for an object
|
||||||
*/
|
*/
|
||||||
public abstract fun buildGeometry(obj: T): BufferGeometry
|
public abstract fun buildGeometry(obj: T): BufferGeometry
|
||||||
|
|
||||||
override fun invoke(obj: T): Mesh {
|
override fun invoke(three: ThreePlugin, obj: T): Mesh {
|
||||||
val geometry = buildGeometry(obj)
|
val geometry = buildGeometry(obj)
|
||||||
|
|
||||||
//JS sometimes tries to pass Geometry as BufferGeometry
|
//JS sometimes tries to pass Geometry as BufferGeometry
|
||||||
@ -36,58 +34,66 @@ public abstract class MeshThreeFactory<in T : Solid>(
|
|||||||
|
|
||||||
//val meshMeta: Meta = obj.properties[Material3D.MATERIAL_KEY]?.node ?: Meta.empty
|
//val meshMeta: Meta = obj.properties[Material3D.MATERIAL_KEY]?.node ?: Meta.empty
|
||||||
|
|
||||||
val mesh = Mesh(geometry, null).apply{
|
val mesh = Mesh(geometry, ThreeMaterials.DEFAULT).apply {
|
||||||
matrixAutoUpdate = false
|
matrixAutoUpdate = false
|
||||||
//set position for mesh
|
//set position for mesh
|
||||||
updatePosition(obj)
|
updatePosition(obj)
|
||||||
}.applyProperties(obj)
|
}.applyProperties(obj)
|
||||||
|
|
||||||
//add listener to object properties
|
//add listener to object properties
|
||||||
obj.onPropertyChange(this) { name ->
|
obj.onPropertyChange(three.updateScope) { name ->
|
||||||
when {
|
when {
|
||||||
name.startsWith(Solid.GEOMETRY_KEY) -> {
|
name.startsWith(Solid.GEOMETRY_KEY) -> {
|
||||||
val oldGeometry = mesh.geometry as BufferGeometry
|
val oldGeometry = mesh.geometry as BufferGeometry
|
||||||
val newGeometry = buildGeometry(obj)
|
val newGeometry = buildGeometry(obj)
|
||||||
oldGeometry.attributes = newGeometry.attributes
|
oldGeometry.attributes = newGeometry.attributes
|
||||||
mesh.applyWireFrame(obj)
|
//mesh.applyWireFrame(obj)
|
||||||
mesh.applyEdges(obj)
|
mesh.applyEdges(obj)
|
||||||
newGeometry.dispose()
|
newGeometry.dispose()
|
||||||
}
|
}
|
||||||
name.startsWith(WIREFRAME_KEY) -> mesh.applyWireFrame(obj)
|
//name.startsWith(WIREFRAME_KEY) -> mesh.applyWireFrame(obj)
|
||||||
name.startsWith(EDGES_KEY) -> mesh.applyEdges(obj)
|
name.startsWith(EDGES_KEY) -> mesh.applyEdges(obj)
|
||||||
else -> mesh.updateProperty(obj, name)
|
else -> mesh.updateProperty(obj, name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return mesh
|
return mesh
|
||||||
}
|
}
|
||||||
|
|
||||||
public companion object {
|
public companion object {
|
||||||
public val EDGES_KEY: Name = "edges".asName()
|
public val EDGES_KEY: Name = "edges".asName()
|
||||||
public val WIREFRAME_KEY: Name = "wireframe".asName()
|
//public val WIREFRAME_KEY: Name = "wireframe".asName()
|
||||||
public val ENABLED_KEY: Name = "enabled".asName()
|
public val ENABLED_KEY: Name = "enabled".asName()
|
||||||
public val EDGES_ENABLED_KEY: Name = EDGES_KEY + ENABLED_KEY
|
public val EDGES_ENABLED_KEY: Name = EDGES_KEY + ENABLED_KEY
|
||||||
public val EDGES_MATERIAL_KEY: Name = EDGES_KEY + SolidMaterial.MATERIAL_KEY
|
public val EDGES_MATERIAL_KEY: Name = EDGES_KEY + SolidMaterial.MATERIAL_KEY
|
||||||
public val WIREFRAME_ENABLED_KEY: Name = WIREFRAME_KEY + ENABLED_KEY
|
//public val WIREFRAME_ENABLED_KEY: Name = WIREFRAME_KEY + ENABLED_KEY
|
||||||
public val WIREFRAME_MATERIAL_KEY: Name = WIREFRAME_KEY + SolidMaterial.MATERIAL_KEY
|
//public val WIREFRAME_MATERIAL_KEY: Name = WIREFRAME_KEY + SolidMaterial.MATERIAL_KEY
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Mesh.applyProperties(obj: Solid): Mesh = apply{
|
internal fun Mesh.applyProperties(obj: Solid): Mesh = apply {
|
||||||
material = getMaterial(obj, true)
|
updateMaterial(obj)
|
||||||
applyEdges(obj)
|
applyEdges(obj)
|
||||||
applyWireFrame(obj)
|
//applyWireFrame(obj)
|
||||||
layers.enable(obj.layer)
|
layers.enable(obj.layer)
|
||||||
children.forEach {
|
children.forEach {
|
||||||
it.layers.enable(obj.layer)
|
it.layers.enable(obj.layer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Mesh.applyEdges(obj: Solid) {
|
public fun Mesh.applyEdges(obj: Solid) {
|
||||||
val edges = children.find { it.name == "@edges" } as? LineSegments
|
val edges = children.find { it.name == "@edges" } as? LineSegments
|
||||||
//inherited edges definition, enabled by default
|
//inherited edges definition, enabled by default
|
||||||
if (obj.getProperty(MeshThreeFactory.EDGES_ENABLED_KEY).boolean != false) {
|
if (obj.getProperty(MeshThreeFactory.EDGES_ENABLED_KEY, inherit = true, includeStyles = true).boolean != false) {
|
||||||
val bufferGeometry = geometry as? BufferGeometry ?: return
|
val bufferGeometry = geometry as? BufferGeometry ?: return
|
||||||
val material = ThreeMaterials.getLineMaterial(obj.getProperty(MeshThreeFactory.EDGES_MATERIAL_KEY).node, true)
|
val material = ThreeMaterials.getLineMaterial(
|
||||||
|
obj.getProperty(
|
||||||
|
MeshThreeFactory.EDGES_MATERIAL_KEY,
|
||||||
|
inherit = true,
|
||||||
|
includeStyles = true
|
||||||
|
).node,
|
||||||
|
true
|
||||||
|
)
|
||||||
if (edges == null) {
|
if (edges == null) {
|
||||||
add(
|
add(
|
||||||
LineSegments(
|
LineSegments(
|
||||||
@ -108,22 +114,23 @@ fun Mesh.applyEdges(obj: Solid) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Mesh.applyWireFrame(obj: Solid) {
|
//public fun Mesh.applyWireFrame(obj: Solid) {
|
||||||
children.find { it.name == "@wireframe" }?.let {
|
// children.find { it.name == "@wireframe" }?.let {
|
||||||
remove(it)
|
// remove(it)
|
||||||
(it as LineSegments).dispose()
|
// (it as LineSegments).dispose()
|
||||||
}
|
// }
|
||||||
//inherited wireframe definition, disabled by default
|
// //inherited wireframe definition, disabled by default
|
||||||
if (obj.getProperty(MeshThreeFactory.WIREFRAME_ENABLED_KEY).boolean == true) {
|
// if (obj.getProperty(MeshThreeFactory.WIREFRAME_ENABLED_KEY).boolean == true) {
|
||||||
val bufferGeometry = geometry as? BufferGeometry ?: return
|
// val bufferGeometry = geometry as? BufferGeometry ?: return
|
||||||
val material = ThreeMaterials.getLineMaterial(obj.getProperty(MeshThreeFactory.WIREFRAME_MATERIAL_KEY).node, true)
|
// val material =
|
||||||
add(
|
// ThreeMaterials.getLineMaterial(obj.getProperty(MeshThreeFactory.WIREFRAME_MATERIAL_KEY).node, true)
|
||||||
LineSegments(
|
// add(
|
||||||
WireframeGeometry(bufferGeometry),
|
// LineSegments(
|
||||||
material
|
// WireframeGeometry(bufferGeometry),
|
||||||
).apply {
|
// material
|
||||||
name = "@wireframe"
|
// ).apply {
|
||||||
}
|
// name = "@wireframe"
|
||||||
)
|
// }
|
||||||
}
|
// )
|
||||||
}
|
// }
|
||||||
|
//}
|
@ -1,6 +1,6 @@
|
|||||||
package hep.dataforge.vision.solid.three
|
package hep.dataforge.vision.solid.three
|
||||||
|
|
||||||
import hep.dataforge.meta.getItem
|
import hep.dataforge.meta.get
|
||||||
import hep.dataforge.meta.string
|
import hep.dataforge.meta.string
|
||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.Name
|
||||||
import hep.dataforge.names.plus
|
import hep.dataforge.names.plus
|
||||||
@ -20,6 +20,7 @@ import info.laht.threekt.external.controls.OrbitControls
|
|||||||
import info.laht.threekt.external.controls.TrackballControls
|
import info.laht.threekt.external.controls.TrackballControls
|
||||||
import info.laht.threekt.geometries.EdgesGeometry
|
import info.laht.threekt.geometries.EdgesGeometry
|
||||||
import info.laht.threekt.helpers.AxesHelper
|
import info.laht.threekt.helpers.AxesHelper
|
||||||
|
import info.laht.threekt.lights.AmbientLight
|
||||||
import info.laht.threekt.materials.LineBasicMaterial
|
import info.laht.threekt.materials.LineBasicMaterial
|
||||||
import info.laht.threekt.math.Vector2
|
import info.laht.threekt.math.Vector2
|
||||||
import info.laht.threekt.objects.LineSegments
|
import info.laht.threekt.objects.LineSegments
|
||||||
@ -50,8 +51,11 @@ public class ThreeCanvas(
|
|||||||
public var axes: AxesHelper = AxesHelper(options.axes.size.toInt()).apply { visible = options.axes.visible }
|
public var axes: AxesHelper = AxesHelper(options.axes.size.toInt()).apply { visible = options.axes.visible }
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
private var light = buildLight(options.light)
|
||||||
|
|
||||||
private val scene: Scene = Scene().apply {
|
private val scene: Scene = Scene().apply {
|
||||||
add(axes)
|
add(axes)
|
||||||
|
add(light)
|
||||||
}
|
}
|
||||||
|
|
||||||
public var camera: PerspectiveCamera = buildCamera(options.camera)
|
public var camera: PerspectiveCamera = buildCamera(options.camera)
|
||||||
@ -66,6 +70,7 @@ public class ThreeCanvas(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val canvas = (renderer.domElement as HTMLCanvasElement).apply {
|
private val canvas = (renderer.domElement as HTMLCanvasElement).apply {
|
||||||
|
className += "three-canvas"
|
||||||
width = 600
|
width = 600
|
||||||
height = 600
|
height = 600
|
||||||
style.apply {
|
style.apply {
|
||||||
@ -130,7 +135,8 @@ public class ThreeCanvas(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun attach(element: Element) {
|
internal fun attach(element: Element) {
|
||||||
|
check(element.getElementsByClassName("three-canvas").length == 0){"Three canvas already created in this element"}
|
||||||
element.appendChild(canvas)
|
element.appendChild(canvas)
|
||||||
updateSize()
|
updateSize()
|
||||||
}
|
}
|
||||||
@ -163,6 +169,7 @@ public class ThreeCanvas(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun buildLight(spec: Light): info.laht.threekt.lights.Light = AmbientLight(0x404040)
|
||||||
|
|
||||||
private fun buildCamera(spec: Camera) = PerspectiveCamera(
|
private fun buildCamera(spec: Camera) = PerspectiveCamera(
|
||||||
spec.fov,
|
spec.fov,
|
||||||
@ -176,7 +183,7 @@ public class ThreeCanvas(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun addControls(element: Node, controls: Controls) {
|
private fun addControls(element: Node, controls: Controls) {
|
||||||
when (controls.getItem("type").string) {
|
when (controls.get("type").string) {
|
||||||
"trackball" -> TrackballControls(camera, element)
|
"trackball" -> TrackballControls(camera, element)
|
||||||
else -> OrbitControls(camera, element)
|
else -> OrbitControls(camera, element)
|
||||||
}
|
}
|
||||||
@ -189,8 +196,10 @@ public class ThreeCanvas(
|
|||||||
}
|
}
|
||||||
|
|
||||||
public override fun render(vision: Solid) {
|
public override fun render(vision: Solid) {
|
||||||
//clear old root
|
scene.children.find { it.name == "@root" }?.let {
|
||||||
clear()
|
//Throw error is something is already rendered here
|
||||||
|
error("Root object already is present in the canvas")
|
||||||
|
}
|
||||||
|
|
||||||
val object3D = three.buildObject3D(vision)
|
val object3D = three.buildObject3D(vision)
|
||||||
object3D.name = "@root"
|
object3D.name = "@root"
|
||||||
|
@ -22,7 +22,7 @@ import kotlin.reflect.KClass
|
|||||||
public object ThreeCanvasLabelFactory : ThreeFactory<SolidLabel> {
|
public object ThreeCanvasLabelFactory : ThreeFactory<SolidLabel> {
|
||||||
override val type: KClass<in SolidLabel> get() = SolidLabel::class
|
override val type: KClass<in SolidLabel> get() = SolidLabel::class
|
||||||
|
|
||||||
override fun invoke(obj: SolidLabel): Object3D {
|
override fun invoke(three: ThreePlugin, obj: SolidLabel): Object3D {
|
||||||
val canvas = document.createElement("canvas") as HTMLCanvasElement
|
val canvas = document.createElement("canvas") as HTMLCanvasElement
|
||||||
val context = canvas.getContext("2d") as CanvasRenderingContext2D
|
val context = canvas.getContext("2d") as CanvasRenderingContext2D
|
||||||
context.font = "Bold ${obj.fontSize}pt ${obj.fontFamily}"
|
context.font = "Bold ${obj.fontSize}pt ${obj.fontFamily}"
|
||||||
|
@ -2,11 +2,10 @@ package hep.dataforge.vision.solid.three
|
|||||||
|
|
||||||
import hep.dataforge.vision.solid.Convex
|
import hep.dataforge.vision.solid.Convex
|
||||||
import info.laht.threekt.external.geometries.ConvexBufferGeometry
|
import info.laht.threekt.external.geometries.ConvexBufferGeometry
|
||||||
import info.laht.threekt.math.Vector3
|
|
||||||
|
|
||||||
public object ThreeConvexFactory : MeshThreeFactory<Convex>(Convex::class) {
|
public object ThreeConvexFactory : MeshThreeFactory<Convex>(Convex::class) {
|
||||||
override fun buildGeometry(obj: Convex): ConvexBufferGeometry {
|
override fun buildGeometry(obj: Convex): ConvexBufferGeometry {
|
||||||
@Suppress("USELESS_CAST") val vectors = obj.points.toTypedArray() as Array<Vector3>
|
val vectors = obj.points.map { it.toVector() }.toTypedArray()
|
||||||
return ConvexBufferGeometry(vectors)
|
return ConvexBufferGeometry(vectors)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -7,7 +7,6 @@ import hep.dataforge.vision.Vision
|
|||||||
import hep.dataforge.vision.solid.*
|
import hep.dataforge.vision.solid.*
|
||||||
import hep.dataforge.vision.solid.SolidMaterial.Companion.MATERIAL_KEY
|
import hep.dataforge.vision.solid.SolidMaterial.Companion.MATERIAL_KEY
|
||||||
import hep.dataforge.vision.solid.three.ThreeFactory.Companion.TYPE
|
import hep.dataforge.vision.solid.three.ThreeFactory.Companion.TYPE
|
||||||
import hep.dataforge.vision.solid.three.ThreeMaterials.getMaterial
|
|
||||||
import hep.dataforge.vision.visible
|
import hep.dataforge.vision.visible
|
||||||
import info.laht.threekt.core.BufferGeometry
|
import info.laht.threekt.core.BufferGeometry
|
||||||
import info.laht.threekt.core.Object3D
|
import info.laht.threekt.core.Object3D
|
||||||
@ -22,7 +21,7 @@ public interface ThreeFactory<in T : Vision> {
|
|||||||
|
|
||||||
public val type: KClass<in T>
|
public val type: KClass<in T>
|
||||||
|
|
||||||
public operator fun invoke(obj: T): Object3D
|
public operator fun invoke(three: ThreePlugin, obj: T): Object3D
|
||||||
|
|
||||||
public companion object {
|
public companion object {
|
||||||
public const val TYPE: String = "threeFactory"
|
public const val TYPE: String = "threeFactory"
|
||||||
@ -34,7 +33,7 @@ public interface ThreeFactory<in T : Vision> {
|
|||||||
*/
|
*/
|
||||||
public fun Object3D.updatePosition(obj: Vision) {
|
public fun Object3D.updatePosition(obj: Vision) {
|
||||||
visible = obj.visible ?: true
|
visible = obj.visible ?: true
|
||||||
if(obj is Solid) {
|
if (obj is Solid) {
|
||||||
position.set(obj.x, obj.y, obj.z)
|
position.set(obj.x, obj.y, obj.z)
|
||||||
setRotationFromEuler(obj.euler)
|
setRotationFromEuler(obj.euler)
|
||||||
scale.set(obj.scaleX, obj.scaleY, obj.scaleZ)
|
scale.set(obj.scaleX, obj.scaleY, obj.scaleZ)
|
||||||
@ -47,10 +46,10 @@ public fun Object3D.updatePosition(obj: Vision) {
|
|||||||
*/
|
*/
|
||||||
public fun Object3D.updateProperty(source: Vision, propertyName: Name) {
|
public fun Object3D.updateProperty(source: Vision, propertyName: Name) {
|
||||||
if (this is Mesh && propertyName.startsWith(MATERIAL_KEY)) {
|
if (this is Mesh && propertyName.startsWith(MATERIAL_KEY)) {
|
||||||
this.material = getMaterial(source, false)
|
updateMaterialProperty(source, propertyName)
|
||||||
} else if (
|
} else if (
|
||||||
propertyName.startsWith(Solid.POSITION_KEY)
|
propertyName.startsWith(Solid.POSITION_KEY)
|
||||||
|| propertyName.startsWith(Solid.ROTATION)
|
|| propertyName.startsWith(Solid.ROTATION_KEY)
|
||||||
|| propertyName.startsWith(Solid.SCALE_KEY)
|
|| propertyName.startsWith(Solid.SCALE_KEY)
|
||||||
) {
|
) {
|
||||||
//update position of mesh using this object
|
//update position of mesh using this object
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package hep.dataforge.vision.solid.three
|
package hep.dataforge.vision.solid.three
|
||||||
|
|
||||||
|
|
||||||
|
import hep.dataforge.context.logger
|
||||||
import hep.dataforge.vision.solid.SolidLabel
|
import hep.dataforge.vision.solid.SolidLabel
|
||||||
import hep.dataforge.vision.solid.three.ThreeMaterials.getMaterial
|
|
||||||
import info.laht.threekt.core.Object3D
|
import info.laht.threekt.core.Object3D
|
||||||
import info.laht.threekt.geometries.TextBufferGeometry
|
import info.laht.threekt.geometries.TextBufferGeometry
|
||||||
import info.laht.threekt.objects.Mesh
|
import info.laht.threekt.objects.Mesh
|
||||||
@ -15,17 +15,19 @@ import kotlin.reflect.KClass
|
|||||||
public object ThreeLabelFactory : ThreeFactory<SolidLabel> {
|
public object ThreeLabelFactory : ThreeFactory<SolidLabel> {
|
||||||
override val type: KClass<in SolidLabel> get() = SolidLabel::class
|
override val type: KClass<in SolidLabel> get() = SolidLabel::class
|
||||||
|
|
||||||
override fun invoke(obj: SolidLabel): Object3D {
|
override fun invoke(three: ThreePlugin, obj: SolidLabel): Object3D {
|
||||||
val textGeo = TextBufferGeometry(obj.text, jsObject {
|
val textGeo = TextBufferGeometry(obj.text, jsObject {
|
||||||
font = obj.fontFamily
|
font = obj.fontFamily
|
||||||
size = 20
|
size = 20
|
||||||
height = 1
|
height = 1
|
||||||
curveSegments = 1
|
curveSegments = 1
|
||||||
})
|
})
|
||||||
return Mesh(textGeo, getMaterial(obj,true)).apply {
|
return Mesh(textGeo, ThreeMaterials.DEFAULT).apply {
|
||||||
|
updateMaterial(obj)
|
||||||
updatePosition(obj)
|
updatePosition(obj)
|
||||||
obj.onPropertyChange(this@ThreeLabelFactory) { _ ->
|
obj.onPropertyChange(three.updateScope) { _ ->
|
||||||
//TODO
|
//TODO
|
||||||
|
three.logger.warn { "Label parameter change not implemented" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ import kotlin.reflect.KClass
|
|||||||
public object ThreeLineFactory : ThreeFactory<PolyLine> {
|
public object ThreeLineFactory : ThreeFactory<PolyLine> {
|
||||||
override val type: KClass<PolyLine> get() = PolyLine::class
|
override val type: KClass<PolyLine> get() = PolyLine::class
|
||||||
|
|
||||||
override fun invoke(obj: PolyLine): Object3D {
|
override fun invoke(three: ThreePlugin, obj: PolyLine): Object3D {
|
||||||
val geometry = Geometry().apply {
|
val geometry = Geometry().apply {
|
||||||
vertices = Array(obj.points.size) { obj.points[it].toVector() }
|
vertices = Array(obj.points.size) { obj.points[it].toVector() }
|
||||||
}
|
}
|
||||||
@ -28,7 +28,7 @@ public object ThreeLineFactory : ThreeFactory<PolyLine> {
|
|||||||
updatePosition(obj)
|
updatePosition(obj)
|
||||||
//layers.enable(obj.layer)
|
//layers.enable(obj.layer)
|
||||||
//add listener to object properties
|
//add listener to object properties
|
||||||
obj.onPropertyChange(this) { propertyName ->
|
obj.onPropertyChange(three.updateScope) { propertyName ->
|
||||||
updateProperty(obj, propertyName)
|
updateProperty(obj, propertyName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,27 @@
|
|||||||
package hep.dataforge.vision.solid.three
|
package hep.dataforge.vision.solid.three
|
||||||
|
|
||||||
import hep.dataforge.meta.*
|
import hep.dataforge.meta.*
|
||||||
|
import hep.dataforge.names.Name
|
||||||
import hep.dataforge.values.ValueType
|
import hep.dataforge.values.ValueType
|
||||||
import hep.dataforge.values.int
|
import hep.dataforge.values.int
|
||||||
import hep.dataforge.values.string
|
import hep.dataforge.values.string
|
||||||
import hep.dataforge.vision.Colors
|
import hep.dataforge.vision.Colors
|
||||||
import hep.dataforge.vision.Vision
|
import hep.dataforge.vision.Vision
|
||||||
|
import hep.dataforge.vision.allStyles
|
||||||
import hep.dataforge.vision.solid.SolidMaterial
|
import hep.dataforge.vision.solid.SolidMaterial
|
||||||
import info.laht.threekt.materials.LineBasicMaterial
|
import info.laht.threekt.materials.LineBasicMaterial
|
||||||
import info.laht.threekt.materials.Material
|
import info.laht.threekt.materials.Material
|
||||||
import info.laht.threekt.materials.MeshBasicMaterial
|
import info.laht.threekt.materials.MeshBasicMaterial
|
||||||
import info.laht.threekt.materials.MeshPhongMaterial
|
import info.laht.threekt.materials.MeshPhongMaterial
|
||||||
import info.laht.threekt.math.Color
|
import info.laht.threekt.math.Color
|
||||||
|
import info.laht.threekt.objects.Mesh
|
||||||
|
|
||||||
|
|
||||||
public object ThreeMaterials {
|
public object ThreeMaterials {
|
||||||
public val DEFAULT_COLOR: Color = Color(Colors.darkgreen)
|
public val DEFAULT_COLOR: Color = Color(Colors.darkgreen)
|
||||||
public val DEFAULT: MeshBasicMaterial = MeshBasicMaterial().apply {
|
public val DEFAULT: MeshBasicMaterial = MeshBasicMaterial().apply {
|
||||||
color.set(DEFAULT_COLOR)
|
color.set(DEFAULT_COLOR)
|
||||||
|
cached = true
|
||||||
}
|
}
|
||||||
public val DEFAULT_LINE_COLOR: Color = Color(Colors.black)
|
public val DEFAULT_LINE_COLOR: Color = Color(Colors.black)
|
||||||
public val DEFAULT_LINE: LineBasicMaterial = LineBasicMaterial().apply {
|
public val DEFAULT_LINE: LineBasicMaterial = LineBasicMaterial().apply {
|
||||||
@ -54,11 +58,15 @@ public object ThreeMaterials {
|
|||||||
|
|
||||||
private val materialCache = HashMap<Meta, Material>()
|
private val materialCache = HashMap<Meta, Material>()
|
||||||
|
|
||||||
private fun buildMaterial(meta: Meta): Material {
|
internal fun buildMaterial(meta: Meta): Material {
|
||||||
return if (meta[SolidMaterial.SPECULAR_COLOR_KEY] != null) {
|
return if (meta[SolidMaterial.SPECULAR_COLOR_KEY] != null) {
|
||||||
MeshPhongMaterial().apply {
|
MeshPhongMaterial().apply {
|
||||||
color = meta[SolidMaterial.COLOR_KEY]?.getColor() ?: DEFAULT_COLOR
|
color = meta[SolidMaterial.COLOR_KEY]?.getColor() ?: DEFAULT_COLOR
|
||||||
specular = meta[SolidMaterial.SPECULAR_COLOR_KEY]!!.getColor()
|
specular = meta[SolidMaterial.SPECULAR_COLOR_KEY]!!.getColor()
|
||||||
|
emissive = specular
|
||||||
|
reflectivity = 1.0
|
||||||
|
refractionRatio = 1.0
|
||||||
|
shininess = 100.0
|
||||||
opacity = meta[SolidMaterial.OPACITY_KEY]?.double ?: 1.0
|
opacity = meta[SolidMaterial.OPACITY_KEY]?.double ?: 1.0
|
||||||
transparent = opacity < 1.0
|
transparent = opacity < 1.0
|
||||||
wireframe = meta[SolidMaterial.WIREFRAME_KEY].boolean ?: false
|
wireframe = meta[SolidMaterial.WIREFRAME_KEY].boolean ?: false
|
||||||
@ -75,29 +83,40 @@ public object ThreeMaterials {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun getMaterial(vision3D: Vision, cache: Boolean): Material {
|
internal fun cacheMeta(meta: Meta): Material = materialCache.getOrPut(meta) {
|
||||||
val meta = vision3D.getProperty(SolidMaterial.MATERIAL_KEY).node ?: return DEFAULT
|
buildMaterial(meta).apply {
|
||||||
return if (cache) {
|
cached = true
|
||||||
materialCache.getOrPut(meta) { buildMaterial(meta) }
|
|
||||||
} else {
|
|
||||||
buildMaterial(meta)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// internal fun getMaterial(vision: Vision, cache: Boolean): Material {
|
||||||
|
// val meta = vision.getProperty(SolidMaterial.MATERIAL_KEY, inherit = true).node ?: return DEFAULT
|
||||||
|
// return if (cache) {
|
||||||
|
// materialCache.getOrPut(meta) {
|
||||||
|
// buildMaterial(meta).apply {
|
||||||
|
// cached = true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// buildMaterial(meta)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Infer color based on meta item
|
* Infer color based on meta item
|
||||||
*/
|
*/
|
||||||
public fun MetaItem<*>.getColor(): Color {
|
public fun MetaItem.getColor(): Color {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
is MetaItem.ValueItem -> if (this.value.type == ValueType.NUMBER) {
|
is ValueItem -> if (this.value.type == ValueType.NUMBER) {
|
||||||
val int = value.int
|
val int = value.int
|
||||||
Color(int)
|
Color(int)
|
||||||
} else {
|
} else {
|
||||||
Color(this.value.string)
|
Color(this.value.string)
|
||||||
}
|
}
|
||||||
is MetaItem.NodeItem -> {
|
is NodeItem -> {
|
||||||
Color(
|
Color(
|
||||||
node[Colors.RED_KEY]?.int ?: 0,
|
node[Colors.RED_KEY]?.int ?: 0,
|
||||||
node[Colors.GREEN_KEY]?.int ?: 0,
|
node[Colors.GREEN_KEY]?.int ?: 0,
|
||||||
@ -107,3 +126,76 @@ public fun MetaItem<*>.getColor(): Color {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var Material.cached: Boolean
|
||||||
|
get() = userData["cached"] == true
|
||||||
|
set(value) {
|
||||||
|
userData["cached"] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun Mesh.updateMaterial(vision: Vision) {
|
||||||
|
//val meta = vision.getProperty(SolidMaterial.MATERIAL_KEY, inherit = true).node
|
||||||
|
val ownMaterialMeta = vision.getOwnProperty(SolidMaterial.MATERIAL_KEY)
|
||||||
|
val stylesMaterialMeta = vision.allStyles[SolidMaterial.MATERIAL_KEY]
|
||||||
|
val parentMaterialMeta = vision.parent?.getProperty(
|
||||||
|
SolidMaterial.MATERIAL_KEY,
|
||||||
|
inherit = true,
|
||||||
|
includeStyles = false,
|
||||||
|
includeDefaults = false
|
||||||
|
)
|
||||||
|
|
||||||
|
material = when {
|
||||||
|
ownMaterialMeta == null && stylesMaterialMeta == null && parentMaterialMeta == null -> {
|
||||||
|
//use default is not material properties are defined
|
||||||
|
ThreeMaterials.DEFAULT
|
||||||
|
}
|
||||||
|
ownMaterialMeta == null && parentMaterialMeta == null -> {
|
||||||
|
//If material is style-based, use cached
|
||||||
|
ThreeMaterials.cacheMeta(stylesMaterialMeta.node ?: Meta.EMPTY)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
vision.getProperty(SolidMaterial.MATERIAL_KEY).node?.let {
|
||||||
|
ThreeMaterials.buildMaterial(it)
|
||||||
|
} ?: ThreeMaterials.DEFAULT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun Mesh.updateMaterialProperty(vision: Vision, propertyName: Name) {
|
||||||
|
if (material.cached || propertyName == SolidMaterial.MATERIAL_KEY) {
|
||||||
|
//generate a new material since cached material should not be changed
|
||||||
|
updateMaterial(vision)
|
||||||
|
} else {
|
||||||
|
when (propertyName) {
|
||||||
|
SolidMaterial.MATERIAL_COLOR_KEY -> {
|
||||||
|
material.asDynamic().color = vision.getProperty(
|
||||||
|
SolidMaterial.MATERIAL_COLOR_KEY,
|
||||||
|
inherit = true,
|
||||||
|
includeStyles = true,
|
||||||
|
includeDefaults = false
|
||||||
|
)?.getColor() ?: ThreeMaterials.DEFAULT_COLOR
|
||||||
|
material.needsUpdate = true
|
||||||
|
}
|
||||||
|
SolidMaterial.MATERIAL_OPACITY_KEY -> {
|
||||||
|
val opacity = vision.getProperty(
|
||||||
|
SolidMaterial.MATERIAL_OPACITY_KEY,
|
||||||
|
inherit = true,
|
||||||
|
includeStyles = true,
|
||||||
|
includeDefaults = false
|
||||||
|
).double ?: 1.0
|
||||||
|
material.asDynamic().opacity = opacity
|
||||||
|
material.transparent = opacity < 1.0
|
||||||
|
material.needsUpdate = true
|
||||||
|
}
|
||||||
|
SolidMaterial.MATERIAL_WIREFRAME_KEY -> {
|
||||||
|
material.asDynamic().wireframe = vision.getProperty(
|
||||||
|
SolidMaterial.MATERIAL_WIREFRAME_KEY,
|
||||||
|
inherit = true,
|
||||||
|
includeStyles = true,
|
||||||
|
includeDefaults = false
|
||||||
|
).boolean ?: false
|
||||||
|
material.needsUpdate = true
|
||||||
|
}
|
||||||
|
else -> console.warn("Unrecognized material property: $propertyName")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,9 @@ import hep.dataforge.vision.solid.*
|
|||||||
import hep.dataforge.vision.solid.specifications.Canvas3DOptions
|
import hep.dataforge.vision.solid.specifications.Canvas3DOptions
|
||||||
import hep.dataforge.vision.visible
|
import hep.dataforge.vision.visible
|
||||||
import info.laht.threekt.core.Object3D
|
import info.laht.threekt.core.Object3D
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
import org.w3c.dom.Element
|
import org.w3c.dom.Element
|
||||||
import org.w3c.dom.HTMLElement
|
import org.w3c.dom.HTMLElement
|
||||||
import kotlin.collections.set
|
import kotlin.collections.set
|
||||||
@ -22,7 +25,9 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
|
|||||||
|
|
||||||
private val objectFactories = HashMap<KClass<out Solid>, ThreeFactory<*>>()
|
private val objectFactories = HashMap<KClass<out Solid>, ThreeFactory<*>>()
|
||||||
private val compositeFactory = ThreeCompositeFactory(this)
|
private val compositeFactory = ThreeCompositeFactory(this)
|
||||||
private val refFactory = ThreeReferenceFactory(this)
|
|
||||||
|
//TODO generate a separate supervisor update scope
|
||||||
|
internal val updateScope: CoroutineScope get() = context
|
||||||
|
|
||||||
init {
|
init {
|
||||||
//Add specialized factories here
|
//Add specialized factories here
|
||||||
@ -41,76 +46,66 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
|
|||||||
as ThreeFactory<Solid>?
|
as ThreeFactory<Solid>?
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun buildObject3D(obj: Solid): Object3D {
|
public fun buildObject3D(obj: Solid): Object3D = when (obj) {
|
||||||
return when (obj) {
|
is ThreeVision -> obj.render(this)
|
||||||
is ThreeVision -> obj.render()
|
is SolidReferenceGroup -> ThreeReferenceFactory(this, obj)
|
||||||
is SolidReference -> refFactory(obj)
|
is SolidGroup -> {
|
||||||
is SolidGroup -> {
|
val group = ThreeGroup()
|
||||||
val group = ThreeGroup()
|
obj.children.forEach { (token, child) ->
|
||||||
obj.children.forEach { (token, child) ->
|
if (child is Solid && child.ignore != true) {
|
||||||
if (child is Solid && child.ignore != true) {
|
try {
|
||||||
try {
|
val object3D = buildObject3D(child)
|
||||||
val object3D = buildObject3D(child)
|
group[token] = object3D
|
||||||
group[token] = object3D
|
} catch (ex: Throwable) {
|
||||||
} catch (ex: Throwable) {
|
logger.error(ex) { "Failed to render $child" }
|
||||||
logger.error(ex) { "Failed to render $child" }
|
ex.printStackTrace()
|
||||||
ex.printStackTrace()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
group.apply {
|
|
||||||
updatePosition(obj)
|
|
||||||
//obj.onChildrenChange()
|
|
||||||
|
|
||||||
obj.onPropertyChange(this) { name ->
|
|
||||||
if (
|
|
||||||
name.startsWith(Solid.POSITION_KEY) ||
|
|
||||||
name.startsWith(Solid.ROTATION) ||
|
|
||||||
name.startsWith(Solid.SCALE_KEY)
|
|
||||||
) {
|
|
||||||
//update position of mesh using this object
|
|
||||||
updatePosition(obj)
|
|
||||||
} else if (name == Vision.VISIBLE_KEY) {
|
|
||||||
visible = obj.visible ?: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
obj.onStructureChange(this) { nameToken, _, child ->
|
|
||||||
// if (name.isEmpty()) {
|
|
||||||
// logger.error { "Children change with empty name on $group" }
|
|
||||||
// return@onChildrenChange
|
|
||||||
// }
|
|
||||||
|
|
||||||
// val parentName = name.cutLast()
|
|
||||||
// val childName = name.last()!!
|
|
||||||
|
|
||||||
//removing old object
|
|
||||||
findChild(nameToken.asName())?.let { oldChild ->
|
|
||||||
oldChild.parent?.remove(oldChild)
|
|
||||||
}
|
|
||||||
|
|
||||||
//adding new object
|
|
||||||
if (child != null && child is Solid) {
|
|
||||||
try {
|
|
||||||
val object3D = buildObject3D(child)
|
|
||||||
set(nameToken, object3D)
|
|
||||||
} catch (ex: Throwable) {
|
|
||||||
logger.error(ex) { "Failed to render $child" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is Composite -> compositeFactory(obj)
|
|
||||||
else -> {
|
group.apply {
|
||||||
//find specialized factory for this type if it is present
|
updatePosition(obj)
|
||||||
val factory: ThreeFactory<Solid>? = findObjectFactory(obj::class)
|
//obj.onChildrenChange()
|
||||||
when {
|
|
||||||
factory != null -> factory(obj)
|
obj.onPropertyChange(updateScope) { name ->
|
||||||
obj is GeometrySolid -> ThreeShapeFactory(obj)
|
if (
|
||||||
else -> error("Renderer for ${obj::class} not found")
|
name.startsWith(Solid.POSITION_KEY) ||
|
||||||
|
name.startsWith(Solid.ROTATION_KEY) ||
|
||||||
|
name.startsWith(Solid.SCALE_KEY)
|
||||||
|
) {
|
||||||
|
//update position of mesh using this object
|
||||||
|
updatePosition(obj)
|
||||||
|
} else if (name == Vision.VISIBLE_KEY) {
|
||||||
|
visible = obj.visible ?: true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
obj.structureChanges.onEach { (nameToken, _, child) ->
|
||||||
|
//removing old object
|
||||||
|
findChild(nameToken.asName())?.let { oldChild ->
|
||||||
|
oldChild.parent?.remove(oldChild)
|
||||||
|
}
|
||||||
|
|
||||||
|
//adding new object
|
||||||
|
if (child != null && child is Solid) {
|
||||||
|
try {
|
||||||
|
val object3D = buildObject3D(child)
|
||||||
|
set(nameToken, object3D)
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
logger.error(ex) { "Failed to render $child" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.launchIn(updateScope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is Composite -> compositeFactory(this, obj)
|
||||||
|
else -> {
|
||||||
|
//find specialized factory for this type if it is present
|
||||||
|
val factory: ThreeFactory<Solid>? = findObjectFactory(obj::class)
|
||||||
|
when {
|
||||||
|
factory != null -> factory(this, obj)
|
||||||
|
obj is GeometrySolid -> ThreeShapeFactory(this, obj)
|
||||||
|
else -> error("Renderer for ${obj::class} not found")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -133,14 +128,24 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
|
|||||||
return if (vision is Solid) ElementVisionRenderer.DEFAULT_RATING else ElementVisionRenderer.ZERO_RATING
|
return if (vision is Solid) ElementVisionRenderer.DEFAULT_RATING else ElementVisionRenderer.ZERO_RATING
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public fun renderSolid(
|
||||||
|
element: Element,
|
||||||
|
vision: Solid,
|
||||||
|
options: Canvas3DOptions,
|
||||||
|
): ThreeCanvas = createCanvas(element, options).apply {
|
||||||
|
render(vision)
|
||||||
|
}
|
||||||
|
|
||||||
override fun render(element: Element, vision: Vision, meta: Meta) {
|
override fun render(element: Element, vision: Vision, meta: Meta) {
|
||||||
createCanvas(element, Canvas3DOptions.read(meta)).render(
|
renderSolid(
|
||||||
vision as? Solid ?: error("Solid expected but ${vision::class} is found")
|
element,
|
||||||
|
vision as? Solid ?: error("Solid expected but ${vision::class} is found"),
|
||||||
|
Canvas3DOptions.read(meta)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public companion object : PluginFactory<ThreePlugin> {
|
public companion object : PluginFactory<ThreePlugin> {
|
||||||
override val tag: PluginTag = PluginTag("visual.three", PluginTag.DATAFORGE_GROUP)
|
override val tag: PluginTag = PluginTag("vision.threejs", PluginTag.DATAFORGE_GROUP)
|
||||||
override val type: KClass<ThreePlugin> = ThreePlugin::class
|
override val type: KClass<ThreePlugin> = ThreePlugin::class
|
||||||
override fun invoke(meta: Meta, context: Context): ThreePlugin = ThreePlugin()
|
override fun invoke(meta: Meta, context: Context): ThreePlugin = ThreePlugin()
|
||||||
}
|
}
|
||||||
@ -150,7 +155,7 @@ public fun ThreePlugin.render(
|
|||||||
element: HTMLElement,
|
element: HTMLElement,
|
||||||
obj: Solid,
|
obj: Solid,
|
||||||
options: Canvas3DOptions.() -> Unit = {},
|
options: Canvas3DOptions.() -> Unit = {},
|
||||||
): ThreeCanvas = createCanvas(element, Canvas3DOptions(options)).apply { render(obj) }
|
): ThreeCanvas = renderSolid(element, obj, Canvas3DOptions(options))
|
||||||
|
|
||||||
internal operator fun Object3D.set(token: NameToken, object3D: Object3D) {
|
internal operator fun Object3D.set(token: NameToken, object3D: Object3D) {
|
||||||
object3D.name = token.toString()
|
object3D.name = token.toString()
|
||||||
|
@ -1,20 +1,22 @@
|
|||||||
package hep.dataforge.vision.solid.three
|
package hep.dataforge.vision.solid.three
|
||||||
|
|
||||||
|
import hep.dataforge.meta.node
|
||||||
import hep.dataforge.names.cutFirst
|
import hep.dataforge.names.cutFirst
|
||||||
import hep.dataforge.names.firstOrNull
|
import hep.dataforge.names.firstOrNull
|
||||||
import hep.dataforge.names.toName
|
import hep.dataforge.names.toName
|
||||||
import hep.dataforge.vision.solid.Solid
|
import hep.dataforge.vision.solid.Solid
|
||||||
import hep.dataforge.vision.solid.SolidReference
|
import hep.dataforge.vision.solid.SolidMaterial
|
||||||
import hep.dataforge.vision.solid.SolidReference.Companion.REFERENCE_CHILD_PROPERTY_PREFIX
|
import hep.dataforge.vision.solid.SolidReferenceGroup
|
||||||
|
import hep.dataforge.vision.solid.SolidReferenceGroup.Companion.REFERENCE_CHILD_PROPERTY_PREFIX
|
||||||
import info.laht.threekt.core.BufferGeometry
|
import info.laht.threekt.core.BufferGeometry
|
||||||
import info.laht.threekt.core.Object3D
|
import info.laht.threekt.core.Object3D
|
||||||
import info.laht.threekt.objects.Mesh
|
import info.laht.threekt.objects.Mesh
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
public class ThreeReferenceFactory(public val three: ThreePlugin) : ThreeFactory<SolidReference> {
|
public object ThreeReferenceFactory : ThreeFactory<SolidReferenceGroup> {
|
||||||
private val cache = HashMap<Solid, Object3D>()
|
private val cache = HashMap<Solid, Object3D>()
|
||||||
|
|
||||||
override val type: KClass<SolidReference> = SolidReference::class
|
override val type: KClass<SolidReferenceGroup> = SolidReferenceGroup::class
|
||||||
|
|
||||||
private fun Object3D.replicate(): Object3D {
|
private fun Object3D.replicate(): Object3D {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
@ -30,7 +32,7 @@ public class ThreeReferenceFactory(public val three: ThreePlugin) : ThreeFactory
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun invoke(obj: SolidReference): Object3D {
|
override fun invoke(three: ThreePlugin, obj: SolidReferenceGroup): Object3D {
|
||||||
val template = obj.prototype
|
val template = obj.prototype
|
||||||
val cachedObject = cache.getOrPut(template) {
|
val cachedObject = cache.getOrPut(template) {
|
||||||
three.buildObject3D(template)
|
three.buildObject3D(template)
|
||||||
@ -40,10 +42,13 @@ public class ThreeReferenceFactory(public val three: ThreePlugin) : ThreeFactory
|
|||||||
object3D.updatePosition(obj)
|
object3D.updatePosition(obj)
|
||||||
|
|
||||||
if(object3D is Mesh){
|
if(object3D is Mesh){
|
||||||
|
//object3D.material = ThreeMaterials.buildMaterial(obj.getProperty(SolidMaterial.MATERIAL_KEY).node!!)
|
||||||
object3D.applyProperties(obj)
|
object3D.applyProperties(obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
obj.onPropertyChange(this) { name ->
|
//TODO apply child properties
|
||||||
|
|
||||||
|
obj.onPropertyChange(three.updateScope) { name->
|
||||||
if (name.firstOrNull()?.body == REFERENCE_CHILD_PROPERTY_PREFIX) {
|
if (name.firstOrNull()?.body == REFERENCE_CHILD_PROPERTY_PREFIX) {
|
||||||
val childName = name.firstOrNull()?.index?.toName() ?: error("Wrong syntax for reference child property: '$name'")
|
val childName = name.firstOrNull()?.index?.toName() ?: error("Wrong syntax for reference child property: '$name'")
|
||||||
val propertyName = name.cutFirst()
|
val propertyName = name.cutFirst()
|
||||||
@ -55,6 +60,7 @@ public class ThreeReferenceFactory(public val three: ThreePlugin) : ThreeFactory
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return object3D
|
return object3D
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -7,5 +7,5 @@ import info.laht.threekt.core.Object3D
|
|||||||
* A custom visual object that has its own Three.js renderer
|
* A custom visual object that has its own Three.js renderer
|
||||||
*/
|
*/
|
||||||
public abstract class ThreeVision : SolidBase() {
|
public abstract class ThreeVision : SolidBase() {
|
||||||
public abstract fun render(): Object3D
|
public abstract fun render(three: ThreePlugin): Object3D
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ import kotlin.math.PI
|
|||||||
|
|
||||||
public val Solid.euler: Euler get() = Euler(rotationX, rotationY, rotationZ, rotationOrder.name)
|
public val Solid.euler: Euler get() = Euler(rotationX, rotationY, rotationZ, rotationOrder.name)
|
||||||
|
|
||||||
public val MetaItem<*>.vector: Vector3 get() = Vector3(node["x"].float ?: 0f, node["y"].float ?: 0f, node["z"].float ?: 0f)
|
public val MetaItem.vector: Vector3 get() = Vector3(node["x"].float ?: 0f, node["y"].float ?: 0f, node["z"].float ?: 0f)
|
||||||
|
|
||||||
public fun Geometry.toBufferGeometry(): BufferGeometry = BufferGeometry().apply { fromGeometry(this@toBufferGeometry) }
|
public fun Geometry.toBufferGeometry(): BufferGeometry = BufferGeometry().apply { fromGeometry(this@toBufferGeometry) }
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
package info.laht.threekt.helpers
|
package info.laht.threekt.helpers
|
||||||
|
|
||||||
import info.laht.threekt.core.Object3D
|
import info.laht.threekt.core.Object3D
|
||||||
import info.laht.threekt.lights.HemiSphereLight
|
import info.laht.threekt.lights.HemisphereLight
|
||||||
import info.laht.threekt.lights.Light
|
import info.laht.threekt.lights.Light
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -39,7 +39,7 @@ import info.laht.threekt.lights.Light
|
|||||||
* @param color (optional) if this is not the set the helper will take the color of the light.
|
* @param color (optional) if this is not the set the helper will take the color of the light.
|
||||||
*/
|
*/
|
||||||
external class HemisphereLightHelper(
|
external class HemisphereLightHelper(
|
||||||
light: HemiSphereLight,
|
light: HemisphereLight,
|
||||||
size: Number,
|
size: Number,
|
||||||
color: Int = definedExternally
|
color: Int = definedExternally
|
||||||
) : Object3D {
|
) : Object3D {
|
||||||
|
@ -34,7 +34,7 @@ import info.laht.threekt.math.Color
|
|||||||
*
|
*
|
||||||
* This light cannot be used to cast shadows.
|
* This light cannot be used to cast shadows.
|
||||||
*/
|
*/
|
||||||
external class HemiSphereLight(
|
external class HemisphereLight(
|
||||||
skyColor: Int = definedExternally,
|
skyColor: Int = definedExternally,
|
||||||
groundColor: Int = definedExternally,
|
groundColor: Int = definedExternally,
|
||||||
intensity: Number = definedExternally
|
intensity: Number = definedExternally
|
||||||
@ -42,6 +42,6 @@ external class HemiSphereLight(
|
|||||||
|
|
||||||
var groundColor: Color
|
var groundColor: Color
|
||||||
|
|
||||||
fun copy(light: HemiSphereLight): HemiSphereLight
|
fun copy(light: HemisphereLight): HemisphereLight
|
||||||
|
|
||||||
}
|
}
|
@ -111,7 +111,7 @@ open external class Material {
|
|||||||
|
|
||||||
var visible: Boolean
|
var visible: Boolean
|
||||||
|
|
||||||
var userData: Map<String, Any>
|
var userData: dynamic
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specifies that the material needs to be updated at the WebGL level. Set it to true if you made changes that need to be reflected in WebGL.
|
* Specifies that the material needs to be updated at the WebGL level. Set it to true if you made changes that need to be reflected in WebGL.
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
package hep.dataforge.vision.three.server
|
|
||||||
|
|
||||||
import hep.dataforge.context.Context
|
|
||||||
import hep.dataforge.vision.VisionManager
|
|
||||||
|
|
||||||
public expect val visionContext: Context
|
|
||||||
|
|
||||||
public val visionManager: VisionManager get() = visionContext.plugins.fetch(VisionManager)
|
|
@ -1,46 +1,18 @@
|
|||||||
package hep.dataforge.vision.three.server
|
package hep.dataforge.vision.three.server
|
||||||
|
|
||||||
import hep.dataforge.context.Context
|
|
||||||
import hep.dataforge.context.Global
|
import hep.dataforge.context.Global
|
||||||
import hep.dataforge.vision.client.VisionClient
|
import hep.dataforge.vision.client.VisionClient
|
||||||
import hep.dataforge.vision.client.renderAllVisions
|
import hep.dataforge.vision.client.renderAllVisions
|
||||||
import hep.dataforge.vision.solid.three.ThreePlugin
|
import hep.dataforge.vision.solid.three.ThreePlugin
|
||||||
import kotlinx.browser.window
|
import kotlinx.browser.window
|
||||||
|
|
||||||
//FIXME check plugin loading in JS
|
|
||||||
//public actual val visionContext: Context = Global.context("vision-client") {
|
|
||||||
// //Loading three-js renderer
|
|
||||||
// plugin(ThreePlugin)
|
|
||||||
//}
|
|
||||||
|
|
||||||
public actual val visionContext: Context = Global.context("vision-client").apply {
|
|
||||||
//Loading three-js renderer
|
|
||||||
plugins.fetch(ThreePlugin)
|
|
||||||
}
|
|
||||||
|
|
||||||
public val clientManager: VisionClient get() = visionContext.plugins.fetch(VisionClient)
|
|
||||||
|
|
||||||
|
|
||||||
///**
|
|
||||||
// * Render all visions in the document using registered renderers
|
|
||||||
// */
|
|
||||||
//@JsExport
|
|
||||||
//public fun renderVisions() {
|
|
||||||
// //Fetch from server and render visions for all outputs
|
|
||||||
// window.onload = {
|
|
||||||
// clientManager.renderAllVisions()
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
///**
|
|
||||||
// * Render all visions in a given element, using registered renderers
|
|
||||||
// */
|
|
||||||
//@JsExport
|
|
||||||
//public fun renderAllVisionsAt(element: Element) {
|
|
||||||
// clientManager.renderAllVisionsAt(element)
|
|
||||||
//}
|
|
||||||
|
|
||||||
public fun main() {
|
public fun main() {
|
||||||
|
//Loading three-js renderer
|
||||||
|
val visionContext = Global.context("threejs") {
|
||||||
|
plugin(ThreePlugin)
|
||||||
|
}
|
||||||
|
val clientManager = visionContext.plugins.fetch(VisionClient)
|
||||||
|
|
||||||
//Fetch from server and render visions for all outputs
|
//Fetch from server and render visions for all outputs
|
||||||
window.onload = {
|
window.onload = {
|
||||||
clientManager.renderAllVisions()
|
clientManager.renderAllVisions()
|
||||||
|
@ -1,45 +1,25 @@
|
|||||||
package hep.dataforge.vision.three.server
|
package hep.dataforge.vision.three.server
|
||||||
|
|
||||||
import hep.dataforge.context.Context
|
import hep.dataforge.context.Context
|
||||||
import hep.dataforge.context.Global
|
|
||||||
import hep.dataforge.meta.DFExperimental
|
import hep.dataforge.meta.DFExperimental
|
||||||
import hep.dataforge.vision.ResourceLocation
|
import hep.dataforge.vision.ResourceLocation
|
||||||
import hep.dataforge.vision.html.HtmlVisionFragment
|
import hep.dataforge.vision.html.HtmlVisionFragment
|
||||||
import hep.dataforge.vision.html.VisionOutput
|
import hep.dataforge.vision.makeVisionFile
|
||||||
import hep.dataforge.vision.makeFile
|
|
||||||
import hep.dataforge.vision.scriptHeader
|
import hep.dataforge.vision.scriptHeader
|
||||||
import hep.dataforge.vision.solid.SolidGroup
|
|
||||||
import hep.dataforge.vision.solid.SolidManager
|
|
||||||
import java.nio.file.Files
|
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
public actual val visionContext: Context = Global.context("vision-server") {
|
|
||||||
//Loading solid manager for the backend (it does not know about three
|
|
||||||
plugin(SolidManager)
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun VisionServer.useThreeJs(): Unit {
|
public fun VisionServer.useThreeJs(): Unit {
|
||||||
useScript("js/visionforge-three.js")
|
useScript("js/visionforge-three.js")
|
||||||
// header {
|
|
||||||
// script {
|
|
||||||
// unsafe {
|
|
||||||
// +"renderThreeVisions()"
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@DFExperimental
|
@DFExperimental
|
||||||
public inline fun VisionOutput.solid(block: SolidGroup.() -> Unit): SolidGroup = SolidGroup().apply(block)
|
public fun Context.makeVisionFile(
|
||||||
|
fragment: HtmlVisionFragment,
|
||||||
@OptIn(DFExperimental::class)
|
|
||||||
public fun HtmlVisionFragment.makeFile(
|
|
||||||
path: Path? = null,
|
path: Path? = null,
|
||||||
title: String = "VisionForge page",
|
title: String = "VisionForge page",
|
||||||
resourceLocation: ResourceLocation = ResourceLocation.SYSTEM,
|
resourceLocation: ResourceLocation = ResourceLocation.SYSTEM,
|
||||||
show: Boolean = true,
|
show: Boolean = true,
|
||||||
) {
|
): Unit = makeVisionFile(fragment, path = path, title = title, show = show) { actualPath ->
|
||||||
val actualPath = path ?: Files.createTempFile("tempPlot", ".html")
|
scriptHeader("js/visionforge-three.js", actualPath, resourceLocation)
|
||||||
val scriptHeader = Context.scriptHeader("/js/visionforge-three.js", actualPath, resourceLocation)
|
|
||||||
makeFile(visionManager, path = path, show = show, title = title, headers = arrayOf(scriptHeader))
|
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user