Merge pull request #34 from mipt-npm/new-properties

New properties
This commit is contained in:
Alexander Nozik 2020-12-29 13:35:01 +03:00 committed by GitHub
commit f0f117d4fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
94 changed files with 1881 additions and 1259 deletions

View File

@ -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

View File

@ -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")

View File

@ -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)

View File

@ -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

View File

@ -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()

View File

@ -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
)
} }
} }
} }

View File

@ -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")
} }
} }
} }

View File

@ -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)
} }
} }

View File

@ -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
} }
} }
} }

View File

@ -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"

View File

@ -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"))
} }
} }
} }

View File

@ -0,0 +1,2 @@

View File

@ -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)
} }

View File

@ -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
)
}

View File

@ -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)
}

View File

@ -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)
} }

View File

@ -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",

View File

@ -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

View File

@ -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)
} }

View File

@ -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

View File

@ -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)

View File

@ -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
} }

View File

@ -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)

View File

@ -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

View File

@ -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 {

View File

@ -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)
}

View File

@ -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))

View File

@ -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)
}
} }

View File

@ -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)
}
}
}
} }
} }

View File

@ -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()
}
} }
} }
} }

View File

@ -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())

View File

@ -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)
} }

View File

@ -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)

View File

@ -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)
}

View File

@ -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"
} }
} }

View File

@ -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"
} }
} }
} }

View File

@ -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))

View File

@ -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)
}
}

View File

@ -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()
}

View File

@ -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() }

View File

@ -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")
} }

View File

@ -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

View File

@ -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()

View File

@ -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!!
} }

View File

@ -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)
} }

View File

@ -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()
} }

View File

@ -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

View File

@ -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")

View File

@ -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) }

View File

@ -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

View File

@ -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)
} }
} }
} }

View 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}")
}
}
}
}

View File

@ -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))

View File

@ -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()
}
}

View File

@ -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()
// }
//}

View File

@ -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
) )

View File

@ -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
}

View File

@ -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)

View File

@ -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()

View File

@ -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)
} }

View File

@ -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 }
} }

View File

@ -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
} }

View File

@ -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)

View File

@ -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())
} }

View File

@ -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 {

View File

@ -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)

View File

@ -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 }

View File

@ -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)
}

View File

@ -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()
} }

View File

@ -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

View File

@ -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)
}
}

View File

@ -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)

View File

@ -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") {

View File

@ -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)
} }
} }

View File

@ -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)
}
}

View File

@ -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

View File

@ -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"
) // }
} // )
} // }
//}

View File

@ -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"

View File

@ -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}"

View File

@ -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)
} }
} }

View File

@ -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

View File

@ -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" }
} }
} }
} }

View File

@ -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)
} }
} }

View File

@ -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")
}
}
}

View File

@ -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()

View File

@ -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
} }
} }

View File

@ -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
} }

View File

@ -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) }

View File

@ -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 {

View File

@ -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
} }

View File

@ -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.

View File

@ -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)

View File

@ -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()

View File

@ -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))
} }