All tests pass

This commit is contained in:
Alexander Nozik 2022-08-12 22:16:06 +03:00
parent 9221df785d
commit 0ea1ee056a
No known key found for this signature in database
GPG Key ID: F7FCF2DD25C71357
77 changed files with 372 additions and 314 deletions

View File

@ -1,5 +1,5 @@
plugins { plugins {
id("ru.mipt.npm.gradle.project") id("space.kscience.gradle.project")
// id("org.jetbrains.kotlinx.kover") version "0.5.0" // id("org.jetbrains.kotlinx.kover") version "0.5.0"
} }

View File

@ -1,5 +1,5 @@
plugins { plugins {
id("ru.mipt.npm.gradle.mpp") id("space.kscience.gradle.mpp")
} }
kscience{ kscience{

View File

@ -321,7 +321,7 @@ private fun buildVolume(volume: DGeoVolume, context: RootToSolidContext): Solid?
} }
return if (group.children.isEmpty()) { return if (group.children.isEmpty()) {
null null
} else if (group.items.size == 1 && group.properties.raw == null) { } else if (group.items.size == 1 && group.properties.own == null) {
group.items.values.first().apply { parent = null } group.items.values.first().apply { parent = null }
} else { } else {
group group

View File

@ -1,8 +1,8 @@
import ru.mipt.npm.gradle.DependencyConfiguration import space.kscience.gradle.DependencyConfiguration
import ru.mipt.npm.gradle.FXModule import space.kscience.gradle.FXModule
plugins { plugins {
id("ru.mipt.npm.gradle.mpp") id("space.kscience.gradle.mpp")
application application
} }
@ -36,7 +36,7 @@ kotlin {
jvmMain { jvmMain {
dependencies { dependencies {
implementation(project(":visionforge-fx")) implementation(project(":visionforge-fx"))
implementation("ch.qos.logback:logback-classic:1.2.5") implementation("ch.qos.logback:logback-classic:1.2.11")
} }
} }
jsMain { jsMain {

View File

@ -6,7 +6,6 @@ import space.kscience.dataforge.names.Name
import space.kscience.gdml.GdmlShowCase import space.kscience.gdml.GdmlShowCase
import space.kscience.visionforge.Vision import space.kscience.visionforge.Vision
import space.kscience.visionforge.get import space.kscience.visionforge.get
import space.kscience.visionforge.getPropertyValue
import space.kscience.visionforge.solid.Solid import space.kscience.visionforge.solid.Solid
import space.kscience.visionforge.solid.SolidMaterial import space.kscience.visionforge.solid.SolidMaterial
import space.kscience.visionforge.solid.material import space.kscience.visionforge.solid.material
@ -20,7 +19,7 @@ class GDMLVisionTest {
@Test @Test
fun testCubesStyles(){ fun testCubesStyles(){
val segment = cubes.children["composite-000.segment-0"] as Solid val segment = cubes.children["composite-000.segment-0"] as Solid
println(segment.getPropertyValue(Vision.STYLE_KEY)) println(segment.properties.getValue(Vision.STYLE_KEY))
// println(segment.computePropertyNode(SolidMaterial.MATERIAL_KEY)) // println(segment.computePropertyNode(SolidMaterial.MATERIAL_KEY))
// println(segment.computeProperty(SolidMaterial.MATERIAL_COLOR_KEY)) // println(segment.computeProperty(SolidMaterial.MATERIAL_COLOR_KEY))
@ -34,7 +33,7 @@ class GDMLVisionTest {
fun testPrototypeProperty() { fun testPrototypeProperty() {
val child = cubes[Name.of("composite-000","segment-0")] val child = cubes[Name.of("composite-000","segment-0")]
assertNotNull(child) assertNotNull(child)
child.setPropertyValue(SolidMaterial.MATERIAL_COLOR_KEY, "red".asValue()) child.properties.setValue(SolidMaterial.MATERIAL_COLOR_KEY, "red".asValue())
assertEquals("red", child.getProperty(SolidMaterial.MATERIAL_COLOR_KEY).string) assertEquals("red", child.properties.getProperty(SolidMaterial.MATERIAL_COLOR_KEY).string)
} }
} }

View File

@ -26,6 +26,7 @@ import space.kscience.visionforge.setAsRoot
import space.kscience.visionforge.solid.Solid import space.kscience.visionforge.solid.Solid
import space.kscience.visionforge.solid.Solids import space.kscience.visionforge.solid.Solids
import space.kscience.visionforge.solid.ambientLight import space.kscience.visionforge.solid.ambientLight
import space.kscience.visionforge.solid.set
import styled.css import styled.css
import styled.styledDiv import styled.styledDiv
@ -56,7 +57,7 @@ val GDMLApp = fc<GDMLAppProps>("GDMLApp") { props ->
console.info("Marking layers for file $name") console.info("Marking layers for file $name")
markLayers() markLayers()
ambientLight { ambientLight {
color(Colors.white) color.set(Colors.white)
} }
} }
} }

View File

@ -10,6 +10,7 @@ import space.kscience.visionforge.Colors
import space.kscience.visionforge.gdml.toVision import space.kscience.visionforge.gdml.toVision
import space.kscience.visionforge.react.render import space.kscience.visionforge.react.render
import space.kscience.visionforge.solid.ambientLight import space.kscience.visionforge.solid.ambientLight
import space.kscience.visionforge.solid.set
import space.kscience.visionforge.solid.three.ThreePlugin import space.kscience.visionforge.solid.three.ThreePlugin
import space.kscience.visionforge.startApplication import space.kscience.visionforge.startApplication
import styled.injectGlobal import styled.injectGlobal
@ -46,7 +47,7 @@ private class GDMLDemoApp : Application {
child(GDMLApp) { child(GDMLApp) {
val vision = GdmlShowCase.cubes().toVision().apply { val vision = GdmlShowCase.cubes().toVision().apply {
ambientLight { ambientLight {
color(Colors.white) color.set(Colors.white)
} }
} }
//println(context.plugins.fetch(VisionManager).encodeToString(vision)) //println(context.plugins.fetch(VisionManager).encodeToString(vision))

View File

@ -1,5 +1,5 @@
plugins { plugins {
id("ru.mipt.npm.gradle.js") id("space.kscience.gradle.js")
} }
kscience{ kscience{

View File

@ -1,5 +1,5 @@
plugins { plugins {
id("ru.mipt.npm.gradle.mpp") id("space.kscience.gradle.mpp")
application application
} }

View File

@ -69,7 +69,7 @@ class Model(val manager: VisionManager) {
fun reset() { fun reset() {
map.values.forEach { map.values.forEach {
it.properties[SolidMaterial.MATERIAL_COLOR_KEY] = null it.properties.setProperty(SolidMaterial.MATERIAL_COLOR_KEY, null)
} }
tracks.children.clear() tracks.children.clear()
} }

View File

@ -25,6 +25,7 @@ import space.kscience.visionforge.react.flexRow
import space.kscience.visionforge.ring.ThreeCanvasWithControls import space.kscience.visionforge.ring.ThreeCanvasWithControls
import space.kscience.visionforge.ring.tab import space.kscience.visionforge.ring.tab
import space.kscience.visionforge.solid.ambientLight import space.kscience.visionforge.solid.ambientLight
import space.kscience.visionforge.solid.set
import space.kscience.visionforge.solid.specifications.Canvas3DOptions import space.kscience.visionforge.solid.specifications.Canvas3DOptions
import space.kscience.visionforge.solid.three.edges import space.kscience.visionforge.solid.three.edges
import styled.css import styled.css
@ -57,7 +58,7 @@ val MMApp = fc<MMAppProps>("Muon monitor") { props ->
props.model.root.apply { props.model.root.apply {
edges() edges()
ambientLight{ ambientLight{
color(Colors.white) color.set(Colors.white)
} }
} }
} }

View File

@ -17,6 +17,7 @@ private class MMDemoApp : Application {
val context = Context("MM-demo") { val context = Context("MM-demo") {
plugin(ThreePlugin) plugin(ThreePlugin)
} }
val visionManager = context.fetch(VisionManager) val visionManager = context.fetch(VisionManager)
val model = Model(visionManager) val model = Model(visionManager)

View File

@ -5,7 +5,6 @@
<!-- <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">--> <!-- <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">-->
<title>Three js demo for particle physics</title> <title>Three js demo for particle physics</title>
<script type="text/javascript" src="muon-monitor.js"></script> <script type="text/javascript" src="muon-monitor.js"></script>
<link rel="stylesheet" href="css/custom-bootstrap.css">
</head> </head>
<body class="application"> <body class="application">
<div class="container-fluid max-vh-100" id = "app"> </div> <div class="container-fluid max-vh-100" id = "app"> </div>

View File

@ -1,5 +1,5 @@
plugins { plugins {
id("ru.mipt.npm.gradle.jvm") id("space.kscience.gradle.jvm")
application application
} }

View File

@ -1,8 +1,8 @@
import ru.mipt.npm.gradle.DependencyConfiguration import space.kscience.gradle.DependencyConfiguration
import ru.mipt.npm.gradle.FXModule import space.kscience.gradle.FXModule
plugins { plugins {
id("ru.mipt.npm.gradle.mpp") id("space.kscience.gradle.mpp")
application application
} }

View File

@ -4,7 +4,6 @@ import info.laht.threekt.core.Object3D
import info.laht.threekt.geometries.BoxGeometry import info.laht.threekt.geometries.BoxGeometry
import info.laht.threekt.objects.Mesh import info.laht.threekt.objects.Mesh
import space.kscience.dataforge.meta.asValue import space.kscience.dataforge.meta.asValue
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.int import space.kscience.dataforge.meta.int
import space.kscience.dataforge.meta.number import space.kscience.dataforge.meta.number
import space.kscience.dataforge.names.asName import space.kscience.dataforge.names.asName
@ -43,13 +42,13 @@ internal class VariableBox(val xSize: Number, val ySize: Number) : ThreeJsVision
it.layers.enable(this@VariableBox.layer) it.layers.enable(this@VariableBox.layer)
} }
} }
mesh.scale.z = meta[VALUE].number?.toDouble() ?: 1.0 mesh.scale.z = properties.getValue(VALUE)?.number?.toDouble() ?: 1.0
//add listener to object properties //add listener to object properties
onPropertyChange { name -> onPropertyChange { name ->
when { when {
name == VALUE -> { name == VALUE -> {
val value = meta[VALUE].int ?: 0 val value = properties.getValue(VALUE)?.int ?: 0
val size = value.toFloat() / 255f * 20f val size = value.toFloat() / 255f * 20f
mesh.scale.z = size.toDouble() mesh.scale.z = size.toDouble()
mesh.position.z = size.toDouble() / 2 mesh.position.z = size.toDouble() / 2
@ -69,9 +68,9 @@ internal class VariableBox(val xSize: Number, val ySize: Number) : ThreeJsVision
} }
var value: Int var value: Int
get() = meta[VALUE].int ?: 0 get() = properties.getValue(VALUE)?.int ?: 0
set(value) { set(value) {
setPropertyValue(VALUE, value.asValue()) properties.setValue(VALUE, value.asValue())
} }
companion object { companion object {

View File

@ -6,4 +6,4 @@ kotlin.jupyter.add.scanner=false
org.gradle.parallel=true org.gradle.parallel=true
org.gradle.jvmargs=-Xmx4G org.gradle.jvmargs=-Xmx4G
toolsVersion=0.11.8-kotlin-1.7.10 toolsVersion=0.12.0-kotlin-1.7.20-Beta

View File

@ -1,5 +1,5 @@
plugins { plugins {
id("ru.mipt.npm.gradle.mpp") id("space.kscience.gradle.mpp")
id("org.jetbrains.kotlin.jupyter.api") id("org.jetbrains.kotlin.jupyter.api")
} }
@ -21,5 +21,5 @@ kotlin {
} }
readme { readme {
maturity = ru.mipt.npm.gradle.Maturity.EXPERIMENTAL maturity = space.kscience.gradle.Maturity.EXPERIMENTAL
} }

View File

@ -1,5 +1,5 @@
plugins { plugins {
id("ru.mipt.npm.gradle.mpp") id("space.kscience.gradle.mpp")
} }
description = "Jupyter api artifact for GDML rendering" description = "Jupyter api artifact for GDML rendering"
@ -56,5 +56,5 @@ kscience {
} }
readme { readme {
maturity = ru.mipt.npm.gradle.Maturity.EXPERIMENTAL maturity = space.kscience.gradle.Maturity.EXPERIMENTAL
} }

View File

@ -15,10 +15,10 @@ pluginManagement {
} }
plugins { plugins {
id("ru.mipt.npm.gradle.project") version toolsVersion id("space.kscience.gradle.project") version toolsVersion
id("ru.mipt.npm.gradle.mpp") version toolsVersion id("space.kscience.gradle.mpp") version toolsVersion
id("ru.mipt.npm.gradle.jvm") version toolsVersion id("space.kscience.gradle.jvm") version toolsVersion
id("ru.mipt.npm.gradle.js") version toolsVersion id("space.kscience.gradle.js") version toolsVersion
} }
} }
@ -34,7 +34,7 @@ dependencyResolutionManagement {
versionCatalogs { versionCatalogs {
create("npmlibs") { create("npmlibs") {
from("ru.mipt.npm:version-catalog:$toolsVersion") from("space.kscience:version-catalog:$toolsVersion")
} }
} }
} }

View File

@ -1,6 +1,6 @@
plugins { plugins {
kotlin("js") kotlin("js")
id("ru.mipt.npm.gradle.js") id("space.kscience.gradle.js")
} }
val dataforgeVersion: String by rootProject.extra val dataforgeVersion: String by rootProject.extra

View File

@ -1,5 +1,6 @@
package space.kscience.visionforge.bootstrap package space.kscience.visionforge.bootstrap
import kotlinx.coroutines.GlobalScope
import kotlinx.css.BorderStyle import kotlinx.css.BorderStyle
import kotlinx.css.Color import kotlinx.css.Color
import kotlinx.css.padding import kotlinx.css.padding
@ -15,7 +16,6 @@ import react.RBuilder
import react.dom.attrs import react.dom.attrs
import react.dom.button import react.dom.button
import react.fc import react.fc
import space.kscience.dataforge.meta.withDefault
import space.kscience.visionforge.Vision import space.kscience.visionforge.Vision
import space.kscience.visionforge.encodeToString import space.kscience.visionforge.encodeToString
import space.kscience.visionforge.react.flexColumn import space.kscience.visionforge.react.flexColumn
@ -69,8 +69,8 @@ public val CanvasControls: FC<CanvasControlsProps> = fc("CanvasControls") { prop
} }
} }
propertyEditor( propertyEditor(
ownProperties = props.canvasOptions.meta, scope = props.vision?.manager?.context ?: GlobalScope,
allProperties = props.canvasOptions.meta.withDefault(Canvas3DOptions.descriptor.defaultNode), properties = props.canvasOptions.meta,
descriptor = Canvas3DOptions.descriptor, descriptor = Canvas3DOptions.descriptor,
expanded = false expanded = false
) )

View File

@ -3,13 +3,16 @@ package space.kscience.visionforge.bootstrap
import org.w3c.dom.Element import org.w3c.dom.Element
import react.RBuilder import react.RBuilder
import react.dom.client.createRoot import react.dom.client.createRoot
import react.key
import space.kscience.dataforge.meta.descriptors.MetaDescriptor import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.get
import space.kscience.visionforge.Vision import space.kscience.visionforge.Vision
import space.kscience.visionforge.computeProperties
import space.kscience.visionforge.getStyle import space.kscience.visionforge.getStyle
import space.kscience.visionforge.react.EditorPropertyState
import space.kscience.visionforge.react.PropertyEditor
import space.kscience.visionforge.react.metaViewer import space.kscience.visionforge.react.metaViewer
import space.kscience.visionforge.react.propertyEditor
import space.kscience.visionforge.react.render import space.kscience.visionforge.react.render
import space.kscience.visionforge.root
import space.kscience.visionforge.solid.SolidReference import space.kscience.visionforge.solid.SolidReference
import space.kscience.visionforge.styles import space.kscience.visionforge.styles
@ -20,12 +23,25 @@ public fun RBuilder.visionPropertyEditor(
) { ) {
card("Properties") { card("Properties") {
propertyEditor( child(PropertyEditor){
ownProperties = vision.meta, attrs{
allProperties = vision.computeProperties(), this.key = key?.toString()
descriptor = descriptor, this.meta = vision.properties.root()
key = key this.updates = vision.properties.changes
) this.descriptor = descriptor
this.scope = vision.manager?.context ?: error("Orphan vision could not be observed")
this.getPropertyState = {name->
if(vision.properties.own?.get(name)!= null){
EditorPropertyState.Defined
} else if(vision.properties.root()[name] != null){
// TODO differentiate
EditorPropertyState.Default()
} else {
EditorPropertyState.Undefined
}
}
}
}
} }
val styles = if (vision is SolidReference) { val styles = if (vision is SolidReference) {
(vision.styles + vision.prototype.styles).distinct() (vision.styles + vision.prototype.styles).distinct()

View File

@ -1,5 +1,5 @@
plugins { plugins {
id("ru.mipt.npm.gradle.js") id("space.kscience.gradle.js")
} }
dependencies{ dependencies{

View File

@ -25,7 +25,7 @@ public val MultiSelectChooser: FC<ValueChooserProps> = fc("MultiSelectChooser")
select { select {
attrs { attrs {
multiple = true multiple = true
values = (props.actual.value?.list ?: emptyList()).mapTo(HashSet()) { it.string } values = (props.meta.value?.list ?: emptyList()).mapTo(HashSet()) { it.string }
onChangeFunction = onChange onChangeFunction = onChange
} }
props.descriptor?.allowedValues?.forEach { optionValue -> props.descriptor?.allowedValues?.forEach { optionValue ->

View File

@ -1,13 +1,18 @@
package space.kscience.visionforge.react package space.kscience.visionforge.react
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
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.css.* import kotlinx.css.*
import kotlinx.css.properties.TextDecoration import kotlinx.css.properties.TextDecoration
import kotlinx.html.js.onClickFunction import kotlinx.html.js.onClickFunction
import org.w3c.dom.Element
import org.w3c.dom.events.Event import org.w3c.dom.events.Event
import react.* import react.*
import react.dom.attrs import react.dom.attrs
import react.dom.client.createRoot
import space.kscience.dataforge.meta.* import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.descriptors.MetaDescriptor import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.ValueRequirement import space.kscience.dataforge.meta.descriptors.ValueRequirement
@ -19,17 +24,30 @@ import styled.styledButton
import styled.styledDiv import styled.styledDiv
import styled.styledSpan import styled.styledSpan
/**
* The display state of a property
*/
public sealed class EditorPropertyState {
public object Defined : EditorPropertyState()
public class Default(public val source: String = "unknown") : EditorPropertyState()
public object Undefined : EditorPropertyState()
}
public external interface PropertyEditorProps : Props { public external interface PropertyEditorProps : Props {
/** /**
* Root config object - always non-null * Root config object - always non-null
*/ */
public var meta: ObservableMutableMeta public var meta: MutableMeta
/** public var getPropertyState: (Name) -> EditorPropertyState
* Provide default item (greyed out if used)
*/ public var scope: CoroutineScope
public var withDefault: MetaProvider
public var updates: Flow<Name>
/** /**
* Full path to the displayed node in [meta]. Could be empty * Full path to the displayed node in [meta]. Could be empty
@ -54,7 +72,7 @@ private val PropertyEditorItem: FC<PropertyEditorProps> = fc("PropertyEditorItem
private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) { private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
var expanded: Boolean by useState { props.expanded ?: true } var expanded: Boolean by useState { props.expanded ?: true }
val descriptor: MetaDescriptor? = useMemo(props.descriptor, props.name) { props.descriptor?.get(props.name) } val descriptor: MetaDescriptor? = useMemo(props.descriptor, props.name) { props.descriptor?.get(props.name) }
var ownProperty: ObservableMutableMeta by useState { props.meta.getOrCreate(props.name) } var property: MutableMeta by useState { props.meta.getOrCreate(props.name) }
val keys = useMemo(descriptor) { val keys = useMemo(descriptor) {
buildSet { buildSet {
@ -70,17 +88,18 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
val token = props.name.lastOrNull()?.toString() ?: "Properties" val token = props.name.lastOrNull()?.toString() ?: "Properties"
fun update() { fun update() {
ownProperty = props.meta.getOrCreate(props.name) property = props.meta.getOrCreate(props.name)
} }
useEffect(props.meta) { useEffect(props.meta) {
props.meta.onChange(props) { updatedName -> val job = props.updates.onEach { updatedName ->
if (updatedName == props.name) { if (updatedName == props.name) {
update() update()
} }
} }.launchIn(props.scope)
cleanup { cleanup {
props.meta.removeListener(props) job.cancel()
} }
} }
@ -115,7 +134,7 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
styledSpan { styledSpan {
css { css {
+TreeStyles.treeLabel +TreeStyles.treeLabel
if (ownProperty.isEmpty()) { if (property.isEmpty()) {
+TreeStyles.treeLabelInactive +TreeStyles.treeLabelInactive
} }
} }
@ -131,8 +150,8 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
ValueChooser { ValueChooser {
attrs { attrs {
this.descriptor = descriptor this.descriptor = descriptor
this.meta = ownProperty this.meta = property
this.actual = props.withDefault.getMeta(props.name) ?: ownProperty this.state = props.getPropertyState(props.name)
} }
} }
} }
@ -156,7 +175,7 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
} }
+"\u00D7" +"\u00D7"
attrs { attrs {
if (ownProperty.isEmpty()) { if (property.isEmpty()) {
disabled = true disabled = true
} else { } else {
onClickFunction = removeClick onClickFunction = removeClick
@ -179,9 +198,11 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
attrs { attrs {
this.key = props.name.toString() this.key = props.name.toString()
this.meta = props.meta this.meta = props.meta
this.withDefault = props.withDefault
this.name = props.name + token this.name = props.name + token
this.descriptor = props.descriptor this.descriptor = props.descriptor
this.scope = props.scope
this.getPropertyState = { props.getPropertyState(props.name + token) }
this.updates = props.updates
} }
} }
//configEditor(props.root, props.name + token, props.descriptor, props.default) //configEditor(props.root, props.name + token, props.descriptor, props.default)
@ -197,44 +218,51 @@ public val PropertyEditor: FC<PropertyEditorProps> = fc("PropertyEditor") { prop
attrs { attrs {
this.key = "" this.key = ""
this.meta = props.meta this.meta = props.meta
this.withDefault = props.withDefault
this.name = Name.EMPTY this.name = Name.EMPTY
this.descriptor = props.descriptor this.descriptor = props.descriptor
this.expanded = props.expanded this.expanded = props.expanded
this.scope = props.scope
this.getPropertyState = props.getPropertyState
this.updates = props.updates
} }
} }
} }
@OptIn(ExperimentalCoroutinesApi::class)
public fun RBuilder.propertyEditor( public fun RBuilder.propertyEditor(
ownProperties: ObservableMutableMeta, scope: CoroutineScope,
allProperties: MetaProvider = ownProperties, properties: ObservableMutableMeta,
descriptor: MetaDescriptor? = null, descriptor: MetaDescriptor? = null,
key: Any? = null, key: Any? = null,
expanded: Boolean? = null, expanded: Boolean? = null,
) { ) {
child(PropertyEditor) { child(PropertyEditor) {
attrs { attrs {
this.meta = ownProperties this.meta = properties
this.withDefault = allProperties
this.descriptor = descriptor this.descriptor = descriptor
this.key = key?.toString() ?: "" this.key = key?.toString() ?: ""
this.expanded = expanded this.expanded = expanded
this.scope = scope
this.getPropertyState = { name ->
if (properties[name] != null) {
EditorPropertyState.Defined
} else if (descriptor?.get(name)?.defaultValue != null) {
EditorPropertyState.Default("descriptor")
} else {
EditorPropertyState.Undefined
}
}
this.updates = callbackFlow {
properties.onChange(scope) { name ->
scope.launch {
send(name)
}
}
invokeOnClose {
properties.removeListener(scope)
}
}
} }
} }
} }
public fun RBuilder.configEditor(
config: ObservableMutableMeta,
default: MetaProvider = config,
descriptor: MetaDescriptor? = null,
key: Any? = null,
): Unit = propertyEditor(config, default, descriptor, key = key)
public fun Element.configEditor(
config: ObservableMutableMeta,
default: Meta = config,
descriptor: MetaDescriptor? = null,
key: Any? = null,
): Unit = createRoot(this).render {
configEditor(config, default, descriptor, key = key)
}

View File

@ -20,7 +20,7 @@ import styled.styledInput
@JsExport @JsExport
public val RangeValueChooser: FC<ValueChooserProps> = fc("RangeValueChooser") { props -> public val RangeValueChooser: FC<ValueChooserProps> = fc("RangeValueChooser") { props ->
var innerValue by useState(props.actual.double) var innerValue by useState(props.meta.double)
var rangeDisabled: Boolean by useState(props.meta.value == null) var rangeDisabled: Boolean by useState(props.meta.value == null)
val handleDisable: (Event) -> Unit = { val handleDisable: (Event) -> Unit = {

View File

@ -16,6 +16,7 @@ import space.kscience.dataforge.names.plus
import space.kscience.dataforge.names.startsWith import space.kscience.dataforge.names.startsWith
import space.kscience.visionforge.Vision import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionGroup import space.kscience.visionforge.VisionGroup
import space.kscience.visionforge.asSequence
import space.kscience.visionforge.isEmpty import space.kscience.visionforge.isEmpty
import styled.css import styled.css
import styled.styledDiv import styled.styledDiv
@ -59,9 +60,9 @@ private fun RBuilder.visionTree(props: ObjectTreeProps): Unit {
val obj = props.obj val obj = props.obj
//display as node if any child is visible //display as node if any child is visible
if (obj is VisionGroup<*>) { if (obj is VisionGroup) {
flexRow { flexRow {
if (obj.items.any { !it.key.body.startsWith("@") }) { if (obj.children.keys.any { !it.body.startsWith("@") }) {
styledSpan { styledSpan {
css { css {
+TreeStyles.treeCaret +TreeStyles.treeCaret
@ -81,9 +82,9 @@ private fun RBuilder.visionTree(props: ObjectTreeProps): Unit {
css { css {
+TreeStyles.tree +TreeStyles.tree
} }
obj.items.entries obj.children.asSequence()
.filter { !it.key.toString().startsWith("@") } // ignore statics and other hidden children .filter { !it.first.toString().startsWith("@") } // ignore statics and other hidden children
.sortedBy { (it.value as? VisionGroup<*>)?.isEmpty() ?: true } // ignore empty groups .sortedBy { (it.second as? VisionGroup)?.children?.isEmpty() ?: true } // ignore empty groups
.forEach { (childToken, child) -> .forEach { (childToken, child) ->
styledDiv { styledDiv {
css { css {
@ -92,7 +93,7 @@ private fun RBuilder.visionTree(props: ObjectTreeProps): Unit {
child(ObjectTree) { child(ObjectTree) {
attrs { attrs {
this.name = props.name + childToken this.name = props.name + childToken
this.obj = child.vision this.obj = child
this.selected = props.selected this.selected = props.selected
this.clickCallback = props.clickCallback this.clickCallback = props.clickCallback
} }

View File

@ -27,13 +27,13 @@ import styled.styledSelect
public external interface ValueChooserProps : Props { public external interface ValueChooserProps : Props {
public var descriptor: MetaDescriptor? public var descriptor: MetaDescriptor?
public var meta: ObservableMutableMeta public var meta: MutableMeta
public var actual: Meta public var state: EditorPropertyState
} }
@JsExport @JsExport
public val StringValueChooser: FC<ValueChooserProps> = fc("StringValueChooser") { props -> public val StringValueChooser: FC<ValueChooserProps> = fc("StringValueChooser") { props ->
var value by useState(props.actual.string ?: "") var value by useState(props.meta.string ?: "")
val keyDown: (Event) -> Unit = { event -> val keyDown: (Event) -> Unit = { event ->
if (event.type == "keydown" && event.asDynamic().key == "Enter") { if (event.type == "keydown" && event.asDynamic().key == "Enter") {
value = (event.target as HTMLInputElement).value value = (event.target as HTMLInputElement).value
@ -67,7 +67,7 @@ public val BooleanValueChooser: FC<ValueChooserProps> = fc("BooleanValueChooser"
} }
attrs { attrs {
//this.attributes["indeterminate"] = (props.item == null).toString() //this.attributes["indeterminate"] = (props.item == null).toString()
checked = props.actual.boolean ?: false checked = props.meta.boolean ?: false
onChangeFunction = handleChange onChangeFunction = handleChange
} }
} }
@ -75,7 +75,7 @@ public val BooleanValueChooser: FC<ValueChooserProps> = fc("BooleanValueChooser"
@JsExport @JsExport
public val NumberValueChooser: FC<ValueChooserProps> = fc("NumberValueChooser") { props -> public val NumberValueChooser: FC<ValueChooserProps> = fc("NumberValueChooser") { props ->
var innerValue by useState(props.actual.string ?: "") var innerValue by useState(props.meta.string ?: "")
val keyDown: (Event) -> Unit = { event -> val keyDown: (Event) -> Unit = { event ->
if (event.type == "keydown" && event.asDynamic().key == "Enter") { if (event.type == "keydown" && event.asDynamic().key == "Enter") {
innerValue = (event.target as HTMLInputElement).value innerValue = (event.target as HTMLInputElement).value
@ -113,7 +113,7 @@ public val NumberValueChooser: FC<ValueChooserProps> = fc("NumberValueChooser")
@JsExport @JsExport
public val ComboValueChooser: FC<ValueChooserProps> = fc("ComboValueChooser") { props -> public val ComboValueChooser: FC<ValueChooserProps> = fc("ComboValueChooser") { props ->
var selected by useState(props.actual.string ?: "") var selected by useState(props.meta.string ?: "")
val handleChange: (Event) -> Unit = { val handleChange: (Event) -> Unit = {
selected = (it.target as HTMLSelectElement).value selected = (it.target as HTMLSelectElement).value
props.meta.value = selected.asValue() props.meta.value = selected.asValue()
@ -128,7 +128,7 @@ public val ComboValueChooser: FC<ValueChooserProps> = fc("ComboValueChooser") {
} }
} }
attrs { attrs {
this.value = props.actual.string ?: "" this.value = props.meta.string ?: ""
multiple = false multiple = false
onChangeFunction = handleChange onChangeFunction = handleChange
} }
@ -146,7 +146,7 @@ public val ColorValueChooser: FC<ValueChooserProps> = fc("ColorValueChooser") {
margin(0.px) margin(0.px)
} }
attrs { attrs {
this.value = props.actual.value?.let { value -> this.value = props.meta.value?.let { value ->
if (value.type == ValueType.NUMBER) Colors.rgbToString(value.int) if (value.type == ValueType.NUMBER) Colors.rgbToString(value.int)
else value.string else value.string
} ?: "#000000" } ?: "#000000"

View File

@ -1,5 +1,5 @@
plugins { plugins {
id("ru.mipt.npm.gradle.js") id("space.kscience.gradle.js")
} }
val dataforgeVersion: String by rootProject.extra val dataforgeVersion: String by rootProject.extra

View File

@ -9,18 +9,19 @@ import react.dom.div
import react.dom.span import react.dom.span
import ringui.* import ringui.*
import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.Context
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.isEmpty import space.kscience.dataforge.names.isEmpty
import space.kscience.dataforge.names.length import space.kscience.dataforge.names.length
import space.kscience.visionforge.* import space.kscience.visionforge.Vision
import space.kscience.visionforge.react.ThreeCanvasComponent import space.kscience.visionforge.react.*
import space.kscience.visionforge.react.flexColumn import space.kscience.visionforge.root
import space.kscience.visionforge.react.flexRow import space.kscience.visionforge.setAsRoot
import space.kscience.visionforge.react.propertyEditor
import space.kscience.visionforge.solid.Solid import space.kscience.visionforge.solid.Solid
import space.kscience.visionforge.solid.SolidGroup import space.kscience.visionforge.solid.SolidGroup
import space.kscience.visionforge.solid.specifications.Canvas3DOptions import space.kscience.visionforge.solid.specifications.Canvas3DOptions
import space.kscience.visionforge.visionManager
import styled.css import styled.css
import styled.styledDiv import styled.styledDiv
@ -38,7 +39,7 @@ public fun ThreeCanvasWithControlsProps.solid(block: SolidGroup.() -> Unit) {
} }
} }
public fun ThreeCanvasWithControlsProps.options(block: Canvas3DOptions.() -> Unit){ public fun ThreeCanvasWithControlsProps.options(block: Canvas3DOptions.() -> Unit) {
options = Canvas3DOptions(block) options = Canvas3DOptions(block)
} }
@ -81,14 +82,14 @@ public fun RBuilder.nameCrumbs(name: Name?, link: (Name) -> Unit): Unit = styled
@JsExport @JsExport
public val ThreeCanvasWithControls: FC<ThreeCanvasWithControlsProps> = fc("ThreeViewWithControls") { props -> public val ThreeCanvasWithControls: FC<ThreeCanvasWithControlsProps> = fc("ThreeViewWithControls") { props ->
var selected by useState { props.selected } var selected: Name? by useState { props.selected }
var solid: Solid? by useState(null) var solid: Solid? by useState(null)
useEffect { useEffect {
props.context.launch { props.context.launch {
solid = props.builderOfSolid.await() solid = props.builderOfSolid.await()
//ensure that the solid is properly rooted //ensure that the solid is properly rooted
if(solid?.parent == null){ if (solid?.parent == null) {
solid?.setAsRoot(props.context.visionManager) solid?.setAsRoot(props.context.visionManager)
} }
} }
@ -164,12 +165,25 @@ public val ThreeCanvasWithControls: FC<ThreeCanvasWithControlsProps> = fc("Three
nameCrumbs(selected) { selected = it } nameCrumbs(selected) { selected = it }
} }
IslandContent { IslandContent {
propertyEditor( child(PropertyEditor) {
ownProperties = vision.properties(), attrs {
allProperties = vision.computeProperties(), this.key = selected.toString()
descriptor = vision.descriptor, this.meta = vision.properties.root()
key = selected this.updates = vision.properties.changes
) this.descriptor = vision.descriptor
this.scope = props.context
this.getPropertyState = { name ->
if (vision.properties.own?.get(name) != null) {
EditorPropertyState.Defined
} else if (vision.properties.root()[name] != null) {
// TODO differentiate
EditorPropertyState.Default()
} else {
EditorPropertyState.Undefined
}
}
}
}
} }
} }
} }

View File

@ -4,17 +4,16 @@ import org.w3c.dom.Element
import react.RBuilder import react.RBuilder
import react.dom.client.createRoot import react.dom.client.createRoot
import react.dom.p import react.dom.p
import react.key
import ringui.Island import ringui.Island
import ringui.SmartTabs import ringui.SmartTabs
import ringui.Tab import ringui.Tab
import space.kscience.dataforge.meta.descriptors.MetaDescriptor import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.get
import space.kscience.visionforge.Vision import space.kscience.visionforge.Vision
import space.kscience.visionforge.computeProperties
import space.kscience.visionforge.getStyle import space.kscience.visionforge.getStyle
import space.kscience.visionforge.react.flexColumn import space.kscience.visionforge.react.*
import space.kscience.visionforge.react.metaViewer import space.kscience.visionforge.root
import space.kscience.visionforge.react.propertyEditor
import space.kscience.visionforge.react.render
import space.kscience.visionforge.solid.SolidReference import space.kscience.visionforge.solid.SolidReference
import space.kscience.visionforge.styles import space.kscience.visionforge.styles
@ -31,12 +30,25 @@ public fun RBuilder.ringPropertyEditor(
flexColumn { flexColumn {
Island("Properties") { Island("Properties") {
propertyEditor( child(PropertyEditor) {
ownProperties = vision.meta, attrs {
allProperties = vision.computeProperties(), this.key = key?.toString()
descriptor = descriptor, this.meta = vision.properties.root()
key = key this.updates = vision.properties.changes
) this.descriptor = descriptor
this.scope = vision.manager?.context ?: error("Orphan vision could not be observed")
this.getPropertyState = {name->
if(vision.properties.own?.get(name)!= null){
EditorPropertyState.Defined
} else if(vision.properties.root()[name] != null){
// TODO differentiate
EditorPropertyState.Default()
} else {
EditorPropertyState.Undefined
}
}
}
}
} }
if (styles.isNotEmpty()) { if (styles.isNotEmpty()) {

View File

@ -1,5 +1,6 @@
package space.kscience.visionforge.ring package space.kscience.visionforge.ring
import kotlinx.coroutines.GlobalScope
import kotlinx.css.BorderStyle import kotlinx.css.BorderStyle
import kotlinx.css.Color import kotlinx.css.Color
import kotlinx.css.padding import kotlinx.css.padding
@ -18,7 +19,6 @@ import react.fc
import ringui.Island import ringui.Island
import ringui.SmartTabs import ringui.SmartTabs
import ringui.Tab import ringui.Tab
import space.kscience.dataforge.meta.withDefault
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.visionforge.Vision import space.kscience.visionforge.Vision
import space.kscience.visionforge.encodeToString import space.kscience.visionforge.encodeToString
@ -75,8 +75,8 @@ internal val CanvasControls: FC<CanvasControlsProps> = fc("CanvasControls") { pr
} }
} }
propertyEditor( propertyEditor(
ownProperties = props.options.meta, scope = props.vision?.manager?.context ?: GlobalScope,
allProperties = props.options.meta.withDefault(Canvas3DOptions.descriptor.defaultNode), properties = props.options.meta,
descriptor = Canvas3DOptions.descriptor, descriptor = Canvas3DOptions.descriptor,
expanded = false expanded = false
) )

View File

@ -1,5 +1,5 @@
plugins { plugins {
id("ru.mipt.npm.gradle.mpp") id("space.kscience.gradle.mpp")
} }
val dataforgeVersion: String by rootProject.extra val dataforgeVersion: String by rootProject.extra
@ -28,5 +28,5 @@ kscience{
} }
readme{ readme{
maturity = ru.mipt.npm.gradle.Maturity.DEVELOPMENT maturity = space.kscience.gradle.Maturity.DEVELOPMENT
} }

View File

@ -13,7 +13,7 @@ import kotlin.jvm.JvmInline
@JvmInline @JvmInline
public value class StyleSheet(private val owner: Vision) { public value class StyleSheet(private val owner: Vision) {
private val styleNode: Meta get() = owner.properties[STYLESHEET_KEY] private val styleNode: Meta get() = owner.properties.getProperty(STYLESHEET_KEY)
public val items: Map<NameToken, Meta> get() = styleNode.items public val items: Map<NameToken, Meta> get() = styleNode.items
@ -23,7 +23,7 @@ public value class StyleSheet(private val owner: Vision) {
* 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?) {
owner.properties[STYLESHEET_KEY + key] = style owner.properties.setProperty(STYLESHEET_KEY + key, style)
} }
/** /**
@ -86,7 +86,7 @@ public val Vision.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.getValue(Vision.STYLE_KEY)?.stringList ?: emptyList()) + name styles = (properties.own?.get(Vision.STYLE_KEY)?.stringList ?: emptyList()) + name
} }
@ -94,7 +94,7 @@ public fun Vision.useStyle(name: String) {
* Resolve a style with given name for given [Vision]. The style is not necessarily applied to this [Vision]. * Resolve a style with given name for given [Vision]. The style is not necessarily applied to this [Vision].
*/ */
public fun Vision.getStyle(name: String): Meta? = public fun Vision.getStyle(name: String): Meta? =
properties.raw?.getMeta(StyleSheet.STYLESHEET_KEY + name) ?: parent?.getStyle(name) properties.own?.getMeta(StyleSheet.STYLESHEET_KEY + name) ?: parent?.getStyle(name)
/** /**
* Resolve a property from all styles * Resolve a property from all styles

View File

@ -1,9 +1,9 @@
package space.kscience.visionforge package space.kscience.visionforge
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import space.kscience.dataforge.context.Global
import space.kscience.dataforge.meta.asValue import space.kscience.dataforge.meta.asValue
import space.kscience.dataforge.meta.boolean import space.kscience.dataforge.meta.boolean
import space.kscience.dataforge.meta.descriptors.Described import space.kscience.dataforge.meta.descriptors.Described
@ -30,7 +30,7 @@ public interface Vision : Described {
/** /**
* Owner [VisionManager]. Used to define coroutine scope a serialization * Owner [VisionManager]. Used to define coroutine scope a serialization
*/ */
public val manager: VisionManager get() = parent?.manager ?: Global.visionManager public val manager: VisionManager? get() = parent?.manager
public val properties: MutableVisionProperties public val properties: MutableVisionProperties
@ -67,13 +67,17 @@ public var Vision.visible: Boolean?
/** /**
* Subscribe on property updates. The subscription is bound to the given scope and canceled when the scope is canceled * Subscribe on property updates. The subscription is bound to the given scope and canceled when the scope is canceled
*/ */
public fun Vision.onPropertyChange(callback: (Name) -> Unit): Job = properties.changes.onEach { public fun Vision.onPropertyChange(
scope: CoroutineScope? = manager?.context,
callback: (Name) -> Unit
): Job = properties.changes.onEach {
callback(it) callback(it)
}.launchIn(manager.context) }.launchIn(scope ?: error("Orphan Vision can't observe properties"))
public fun <V : Vision, T> V.useProperty( public fun <V : Vision, T> V.useProperty(
property: KProperty1<V, T>, property: KProperty1<V, T>,
scope: CoroutineScope? = manager?.context,
callBack: V.(T) -> Unit, callBack: V.(T) -> Unit,
): Job { ): Job {
//Pass initial value. //Pass initial value.
@ -82,5 +86,5 @@ public fun <V : Vision, T> V.useProperty(
if (name.startsWith(property.name.asName())) { if (name.startsWith(property.name.asName())) {
callBack(property.get(this@useProperty)) callBack(property.get(this@useProperty))
} }
}.launchIn(manager.context) }.launchIn(scope ?: error("Orphan Vision can't observe properties"))
} }

View File

@ -91,8 +91,8 @@ private fun CoroutineScope.collectChange(
) { ) {
//Collect properties change //Collect properties change
source.onPropertyChange { propertyName -> source.onPropertyChange(this) { propertyName ->
val newItem = source.properties.raw?.get(propertyName) val newItem = source.properties.own?.get(propertyName)
collector().propertyChanged(name, propertyName, newItem) collector().propertyChanged(name, propertyName, newItem)
} }
@ -118,8 +118,8 @@ private fun CoroutineScope.collectChange(
*/ */
public fun Vision.flowChanges( public fun Vision.flowChanges(
collectionDuration: Duration, collectionDuration: Duration,
manager: VisionManager = this.manager,
): Flow<VisionChange> = flow { ): Flow<VisionChange> = flow {
val manager = manager?: error("Orphan vision could not collect changes")
var collector = VisionChangeBuilder(manager) var collector = VisionChangeBuilder(manager)
coroutineScope { coroutineScope {

View File

@ -124,7 +124,7 @@ internal abstract class VisionChildrenImpl(
return items!! return items!!
} }
private val scope: CoroutineScope get() = group.manager.context private val scope: CoroutineScope? get() = group.manager?.context
override val keys: Set<NameToken> get() = items?.keys ?: emptySet() override val keys: Set<NameToken> get() = items?.keys ?: emptySet()
@ -134,7 +134,7 @@ internal abstract class VisionChildrenImpl(
override val changes: SharedFlow<Name> get() = _changes override val changes: SharedFlow<Name> get() = _changes
private fun onChange(name: Name) { private fun onChange(name: Name) {
scope.launch { scope?.launch {
_changes.emit(name) _changes.emit(name)
} }
} }
@ -158,7 +158,7 @@ internal abstract class VisionChildrenImpl(
//set parent //set parent
value.parent = group value.parent = group
//start update jobs (only if the vision is rooted) //start update jobs (only if the vision is rooted)
scope.let { scope -> scope?.let { scope ->
val job = value.children?.changes?.onEach { val job = value.children?.changes?.onEach {
onChange(token + it) onChange(token + it)
}?.launchIn(scope) }?.launchIn(scope)

View File

@ -107,7 +107,8 @@ public abstract class VisionPlugin(meta: Meta = Meta.EMPTY) : AbstractPlugin(met
*/ */
public val Context.visionManager: VisionManager get() = fetch(VisionManager) public val Context.visionManager: VisionManager get() = fetch(VisionManager)
public fun Vision.encodeToString(): String = manager.encodeToString(this) public fun Vision.encodeToString(): String =
manager?.encodeToString(this) ?: error("Orphan vision could not be encoded")
/** /**
* A root vision attached to [VisionManager] * A root vision attached to [VisionManager]

View File

@ -1,5 +1,7 @@
package space.kscience.visionforge package space.kscience.visionforge
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharedFlow
@ -16,14 +18,14 @@ public interface VisionProperties {
/** /**
* Raw Visions own properties without styles, defaults, etc. * Raw Visions own properties without styles, defaults, etc.
*/ */
public val raw: Meta? public val own: Meta?
public val descriptor: MetaDescriptor? public val descriptor: MetaDescriptor?
public fun getValue( public fun getValue(
name: Name, name: Name,
inherit: Boolean, inherit: Boolean? = null,
includeStyles: Boolean, includeStyles: Boolean? = null,
): Value? ): Value?
/** /**
@ -31,10 +33,10 @@ public interface VisionProperties {
* @param inherit toggles parent node property lookup. Null means inference from descriptor. * @param inherit toggles parent node property lookup. Null means inference from descriptor.
* @param includeStyles toggles inclusion of properties from styles. * @param includeStyles toggles inclusion of properties from styles.
*/ */
public operator fun get( public fun getProperty(
name: Name, name: Name,
inherit: Boolean, inherit: Boolean? = null,
includeStyles: Boolean, includeStyles: Boolean? = null,
): Meta ): Meta
public val changes: Flow<Name> public val changes: Flow<Name>
@ -48,10 +50,10 @@ public interface VisionProperties {
public interface MutableVisionProperties : VisionProperties { public interface MutableVisionProperties : VisionProperties {
override operator fun get( override fun getProperty(
name: Name, name: Name,
inherit: Boolean, inherit: Boolean?,
includeStyles: Boolean, includeStyles: Boolean?,
): MutableMeta = VisionPropertiesItem( ): MutableMeta = VisionPropertiesItem(
this, this,
name, name,
@ -60,7 +62,7 @@ public interface MutableVisionProperties : VisionProperties {
) )
public operator fun set( public fun setProperty(
name: Name, name: Name,
node: Meta?, node: Meta?,
) )
@ -84,7 +86,7 @@ private class VisionPropertiesItem(
override val items: Map<NameToken, MutableMeta> override val items: Map<NameToken, MutableMeta>
get() { get() {
val metaKeys = properties.raw?.getMeta(nodeName)?.items?.keys ?: emptySet() val metaKeys = properties.own?.getMeta(nodeName)?.items?.keys ?: emptySet()
val descriptorKeys = descriptor?.children?.map { NameToken(it.key) } ?: emptySet() val descriptorKeys = descriptor?.children?.map { NameToken(it.key) } ?: emptySet()
val defaultKeys = default?.get(nodeName)?.items?.keys ?: emptySet() val defaultKeys = default?.get(nodeName)?.items?.keys ?: emptySet()
val inheritFlag = descriptor?.inherited ?: inherit val inheritFlag = descriptor?.inherited ?: inherit
@ -119,7 +121,7 @@ private class VisionPropertiesItem(
) )
override fun setMeta(name: Name, node: Meta?) { override fun setMeta(name: Name, node: Meta?) {
properties[nodeName + name] = node properties.setProperty(nodeName + name, node)
} }
override fun toString(): String = Meta.toString(this) override fun toString(): String = Meta.toString(this)
@ -131,11 +133,10 @@ public abstract class AbstractVisionProperties(
private val vision: Vision, private val vision: Vision,
) : MutableVisionProperties { ) : MutableVisionProperties {
override val descriptor: MetaDescriptor? get() = vision.descriptor override val descriptor: MetaDescriptor? get() = vision.descriptor
protected val default: Meta? get() = descriptor?.defaultNode
protected abstract var properties: MutableMeta? protected abstract var properties: MutableMeta?
override val raw: Meta? get() = properties override val own: Meta? get() = properties
@Synchronized @Synchronized
protected fun getOrCreateProperties(): MutableMeta { protected fun getOrCreateProperties(): MutableMeta {
@ -149,20 +150,24 @@ public abstract class AbstractVisionProperties(
override fun getValue( override fun getValue(
name: Name, name: Name,
inherit: Boolean, inherit: Boolean?,
includeStyles: Boolean, includeStyles: Boolean?,
): Value? { ): Value? {
raw?.get(name)?.value?.let { return it } val descriptor = descriptor?.get(name)
if (includeStyles) { val inheritFlag = inherit ?: descriptor?.inherited ?: false
val stylesFlag = includeStyles ?: descriptor?.usesStyles ?: true
own?.get(name)?.value?.let { return it }
if (stylesFlag) {
vision.getStyleProperty(name)?.value?.let { return it } vision.getStyleProperty(name)?.value?.let { return it }
} }
if (inherit) { if (inheritFlag) {
vision.parent?.properties?.getValue(name, inherit, includeStyles)?.let { return it } vision.parent?.properties?.getValue(name, inherit, includeStyles)?.let { return it }
} }
return default?.get(name)?.value return descriptor?.defaultValue
} }
override fun set(name: Name, node: Meta?) { override fun setProperty(name: Name, node: Meta?) {
//TODO check old value? //TODO check old value?
if (name.isEmpty()) { if (name.isEmpty()) {
properties = node?.asMutableMeta() properties = node?.asMutableMeta()
@ -188,6 +193,7 @@ public abstract class AbstractVisionProperties(
private val _changes = MutableSharedFlow<Name>(10) private val _changes = MutableSharedFlow<Name>(10)
override val changes: SharedFlow<Name> get() = _changes override val changes: SharedFlow<Name> get() = _changes
@OptIn(DelicateCoroutinesApi::class)
override fun invalidate(propertyName: Name) { override fun invalidate(propertyName: Name) {
if (propertyName == Vision.STYLE_KEY) { if (propertyName == Vision.STYLE_KEY) {
vision.styles.asSequence() vision.styles.asSequence()
@ -198,85 +204,46 @@ public abstract class AbstractVisionProperties(
invalidate(it.key.asName()) invalidate(it.key.asName())
} }
} }
vision.manager.context.launch { (vision.manager?.context ?: GlobalScope).launch {
_changes.emit(propertyName) _changes.emit(propertyName)
} }
} }
} }
public fun VisionProperties.getValue(
name: Name,
inherit: Boolean? = null,
includeStyles: Boolean? = null,
): Value? {
val descriptor = descriptor?.get(name)
val inheritFlag = inherit ?: descriptor?.inherited ?: false
val stylesFlag = includeStyles ?: descriptor?.usesStyles ?: true
return getValue(name, inheritFlag, stylesFlag)
}
public fun VisionProperties.getValue( public fun VisionProperties.getValue(
name: String, name: String,
inherit: Boolean? = null, inherit: Boolean? = null,
includeStyles: Boolean? = null, includeStyles: Boolean? = null,
): Value? = getValue(name.parseAsName(), inherit, includeStyles) ): Value? = getValue(name.parseAsName(), inherit, includeStyles)
/**
* Compute the property based on the provided value descriptor. By default, use Vision own descriptor
*/
public operator fun VisionProperties.get(
name: Name,
inherit: Boolean? = null,
includeStyles: Boolean? = null,
): Meta {
val descriptor: MetaDescriptor? = descriptor?.get(name)
val inheritFlag = inherit ?: descriptor?.inherited ?: false
val stylesFlag = includeStyles ?: descriptor?.usesStyles ?: true
return get(name, inheritFlag, stylesFlag)
}
/** /**
* Get [Vision] property using key as a String * Get [Vision] property using key as a String
*/ */
public operator fun VisionProperties.get( public fun VisionProperties.getProperty(
name: String, name: String,
inherit: Boolean? = null, inherit: Boolean? = null,
includeStyles: Boolean? = null, includeStyles: Boolean? = null,
): Meta = get(name.parseAsName(), inherit, includeStyles) ): Meta = getProperty(name.parseAsName(), inherit, includeStyles)
/**
* Compute the property based on the provided value descriptor. By default, use Vision own descriptor
*/
public operator fun MutableVisionProperties.get(
name: Name,
inherit: Boolean? = null,
includeStyles: Boolean? = null,
): MutableMeta {
val descriptor: MetaDescriptor? = descriptor?.get(name)
val inheritFlag = inherit ?: descriptor?.inherited ?: false
val stylesFlag = includeStyles ?: descriptor?.usesStyles ?: true
return get(name, inheritFlag, stylesFlag)
}
/** /**
* The root property node with given inheritance and style flags * The root property node with given inheritance and style flags
* @param inherit - inherit properties from the [Vision] parent. If null, infer from descriptor
* @param includeStyles - include style information. If null, infer from descriptor
*/ */
public fun MutableVisionProperties.root( public fun MutableVisionProperties.root(
inherit: Boolean? = null, inherit: Boolean? = null,
includeStyles: Boolean? = null, includeStyles: Boolean? = null,
): MutableMeta = get(Name.EMPTY, inherit, includeStyles) ): MutableMeta = getProperty(Name.EMPTY, inherit, includeStyles)
/** /**
* Get [Vision] property using key as a String * Get [Vision] property using key as a String
*/ */
public operator fun MutableVisionProperties.get( public fun MutableVisionProperties.getProperty(
name: String, name: String,
inherit: Boolean? = null, inherit: Boolean? = null,
includeStyles: Boolean? = null, includeStyles: Boolean? = null,
): MutableMeta = get(name.parseAsName(), inherit, includeStyles) ): MutableMeta = getProperty(name.parseAsName(), inherit, includeStyles)
public operator fun MutableVisionProperties.set(name: Name, value: Number): Unit = public operator fun MutableVisionProperties.set(name: Name, value: Number): Unit =

View File

@ -9,7 +9,7 @@ import space.kscience.dataforge.names.Name
import space.kscience.visionforge.* import space.kscience.visionforge.*
//TODO replace by something //TODO replace by something
internal val Vision.mutableProperties get() = properties[Name.EMPTY, false, false] internal val Vision.mutableProperties get() = properties.getProperty(Name.EMPTY, false, false)
@Serializable @Serializable
public abstract class VisionOfHtmlInput : AbstractVision() { public abstract class VisionOfHtmlInput : AbstractVision() {

View File

@ -54,7 +54,7 @@ class HtmlTagTest {
div { div {
h2 { +"Properties" } h2 { +"Properties" }
ul { ul {
vision.properties.raw?.items?.forEach { vision.properties.own?.items?.forEach {
li { li {
a { +it.key.toString() } a { +it.key.toString() }
p { +it.value.toString() } p { +it.value.toString() }

View File

@ -2,7 +2,7 @@ package space.kscience.visionforge.meta
import space.kscience.dataforge.meta.* import space.kscience.dataforge.meta.*
import space.kscience.visionforge.VisionGroup import space.kscience.visionforge.VisionGroup
import space.kscience.visionforge.get import space.kscience.visionforge.getProperty
import space.kscience.visionforge.getValue import space.kscience.visionforge.getValue
import space.kscience.visionforge.set import space.kscience.visionforge.set
import kotlin.test.Test import kotlin.test.Test
@ -30,7 +30,7 @@ internal class VisionPropertyTest {
@Test @Test
fun testPropertyEdit() { fun testPropertyEdit() {
val vision = VisionGroup() val vision = VisionGroup()
vision.properties["fff.ddd"].apply { vision.properties.getProperty("fff.ddd").apply {
value = 2.asValue() value = 2.asValue()
} }
assertEquals(2, vision.properties.getValue("fff.ddd")?.int) assertEquals(2, vision.properties.getValue("fff.ddd")?.int)
@ -40,7 +40,7 @@ internal class VisionPropertyTest {
@Test @Test
fun testPropertyUpdate() { fun testPropertyUpdate() {
val vision = VisionGroup() val vision = VisionGroup()
vision.properties["fff"].updateWith(TestScheme) { vision.properties.getProperty("fff").updateWith(TestScheme) {
ddd = 2 ddd = 2
} }
assertEquals(2, vision.properties.getValue("fff.ddd")?.int) assertEquals(2, vision.properties.getValue("fff.ddd")?.int)

View File

@ -115,7 +115,7 @@ public class VisionClient : AbstractPlugin() {
onopen = { onopen = {
feedbackJob = vision.flowChanges( feedbackJob = vision.flowChanges(
feedbackAggregationTime.milliseconds feedbackAggregationTime.milliseconds,
).onEach { change -> ).onEach { change ->
send(visionManager.encodeToString(change)) send(visionManager.encodeToString(change))
}.launchIn(visionManager.context) }.launchIn(visionManager.context)

View File

@ -1,12 +1,12 @@
plugins { plugins {
id("ru.mipt.npm.gradle.jvm") id("space.kscience.gradle.jvm")
} }
val dataforgeVersion: String by rootProject.extra val dataforgeVersion: String by rootProject.extra
val fxVersion: String by rootProject.extra val fxVersion: String by rootProject.extra
kscience{ kscience{
useFx(ru.mipt.npm.gradle.FXModule.CONTROLS, version = fxVersion) useFx(space.kscience.gradle.FXModule.CONTROLS, version = fxVersion)
} }
dependencies { dependencies {
@ -15,12 +15,12 @@ dependencies {
api("org.fxyz3d:fxyz3d:0.5.4") { api("org.fxyz3d:fxyz3d:0.5.4") {
exclude(module = "slf4j-simple") exclude(module = "slf4j-simple")
} }
api("org.jetbrains.kotlinx:kotlinx-coroutines-javafx:${ru.mipt.npm.gradle.KScienceVersions.coroutinesVersion}") api("org.jetbrains.kotlinx:kotlinx-coroutines-javafx:${space.kscience.gradle.KScienceVersions.coroutinesVersion}")
implementation("eu.mihosoft.vrl.jcsg:jcsg:0.5.7") { implementation("eu.mihosoft.vrl.jcsg:jcsg:0.5.7") {
exclude(module = "slf4j-simple") exclude(module = "slf4j-simple")
} }
} }
readme{ readme{
maturity = ru.mipt.npm.gradle.Maturity.PROTOTYPE maturity = space.kscience.gradle.Maturity.PROTOTYPE
} }

View File

@ -16,7 +16,6 @@ import space.kscience.dataforge.context.*
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.boolean import space.kscience.dataforge.meta.boolean
import space.kscience.dataforge.misc.Type import space.kscience.dataforge.misc.Type
import space.kscience.visionforge.get
import space.kscience.visionforge.solid.FX3DFactory.Companion.TYPE import space.kscience.visionforge.solid.FX3DFactory.Companion.TYPE
import space.kscience.visionforge.solid.SolidMaterial.Companion.MATERIAL_KEY import space.kscience.visionforge.solid.SolidMaterial.Companion.MATERIAL_KEY
import space.kscience.visionforge.solid.SolidMaterial.Companion.MATERIAL_WIREFRAME_KEY import space.kscience.visionforge.solid.SolidMaterial.Companion.MATERIAL_WIREFRAME_KEY
@ -77,7 +76,7 @@ public 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.properties.get(SolidMaterial.MATERIAL_COLOR_KEY).color() obj.properties.getProperty(SolidMaterial.MATERIAL_COLOR_KEY).color()
).apply { ).apply {
this.meshView.cullFace = CullFace.FRONT this.meshView.cullFace = CullFace.FRONT
} }

View File

@ -6,7 +6,6 @@ import space.kscience.dataforge.meta.*
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.startsWith import space.kscience.dataforge.names.startsWith
import space.kscience.visionforge.Vision import space.kscience.visionforge.Vision
import space.kscience.visionforge.get
import space.kscience.visionforge.onPropertyChange import space.kscience.visionforge.onPropertyChange
import tornadofx.* import tornadofx.*
@ -35,7 +34,7 @@ public class VisualObjectFXBinding(public val fx: FX3DPlugin, public val obj: Vi
public operator fun get(key: Name): ObjectBinding<Meta?> { public operator fun get(key: Name): ObjectBinding<Meta?> {
return bindings.getOrPut(key) { return bindings.getOrPut(key) {
object : ObjectBinding<Meta?>() { object : ObjectBinding<Meta?>() {
override fun computeValue(): Meta = obj.properties[key] override fun computeValue(): Meta = obj.properties.getProperty(key)
} }
} }
} }

View File

@ -1,5 +1,5 @@
plugins { plugins {
id("ru.mipt.npm.gradle.mpp") id("space.kscience.gradle.mpp")
} }
kotlin { kotlin {
@ -13,5 +13,10 @@ kotlin {
api("space.kscience:gdml:0.4.0") api("space.kscience:gdml:0.4.0")
} }
} }
jvmTest{
dependencies{
implementation("ch.qos.logback:logback-classic:1.2.11")
}
}
} }
} }

View File

@ -1,5 +1,6 @@
package space.kscience.visionforge.gdml package space.kscience.visionforge.gdml
import space.kscience.dataforge.context.Global
import space.kscience.dataforge.context.info import space.kscience.dataforge.context.info
import space.kscience.dataforge.context.logger import space.kscience.dataforge.context.logger
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
@ -50,7 +51,7 @@ private fun VisionCounterTree.topToBottom(): Sequence<VisionCounterTree> = seque
} }
public fun SolidGroup.markLayers(thresholds: List<Int> = listOf(500, 1000, 20000, 50000)) { public fun SolidGroup.markLayers(thresholds: List<Int> = listOf(500, 1000, 20000, 50000)) {
val logger = manager.context.logger val logger = manager?.context?.logger ?: Global.logger
val counterTree = VisionCounterTree(Name.EMPTY, this, hashMapOf()) val counterTree = VisionCounterTree(Name.EMPTY, this, hashMapOf())
val totalCount = counterTree.childrenCount val totalCount = counterTree.childrenCount
if (totalCount > (thresholds.firstOrNull() ?: 0)) { if (totalCount > (thresholds.firstOrNull() ?: 0)) {

View File

@ -26,7 +26,7 @@ class TestCubes {
val smallBoxPrototype = vision.getPrototype(Name.parse("solids.smallBox")) as? Box val smallBoxPrototype = vision.getPrototype(Name.parse("solids.smallBox")) as? Box
assertNotNull(smallBoxPrototype) assertNotNull(smallBoxPrototype)
assertEquals(30.0, smallBoxPrototype.xSize.toDouble()) assertEquals(30.0, smallBoxPrototype.xSize.toDouble())
val smallBoxVision = vision.children["composite-111.smallBox"]?.unref as? Box val smallBoxVision = vision.children["composite-111.smallBox"]?.prototype as? Box
assertNotNull(smallBoxVision) assertNotNull(smallBoxVision)
assertEquals(30.0, smallBoxVision.xSize.toDouble()) assertEquals(30.0, smallBoxVision.xSize.toDouble())
} }

View File

@ -1,5 +1,5 @@
plugins { plugins {
id("ru.mipt.npm.gradle.mpp") id("space.kscience.gradle.mpp")
} }
val markdownVersion = "0.2.4" val markdownVersion = "0.2.4"

View File

@ -1,5 +1,5 @@
plugins { plugins {
id("ru.mipt.npm.gradle.mpp") id("space.kscience.gradle.mpp")
} }
val plotlyVersion = "0.5.3-dev-1" val plotlyVersion = "0.5.3-dev-1"

View File

@ -16,7 +16,7 @@ import space.kscience.visionforge.root
public class VisionOfPlotly private constructor() : AbstractVision() { public class VisionOfPlotly private constructor() : AbstractVision() {
public constructor(plot: Plot) : this() { public constructor(plot: Plot) : this() {
properties[Name.EMPTY] = plot.meta properties.setProperty(Name.EMPTY, plot.meta)
} }
public val plot: Plot get() = Plot(properties.root().asObservable()) public val plot: Plot get() = Plot(properties.root().asObservable())

View File

@ -1,5 +1,5 @@
plugins { plugins {
id("ru.mipt.npm.gradle.jvm") id("space.kscience.gradle.jvm")
} }
val ktorVersion = npmlibs.versions.ktor.get() val ktorVersion = npmlibs.versions.ktor.get()

View File

@ -92,6 +92,7 @@ public class VisionServer internal constructor(
header() header()
} }
title(title) title(title)
consumer.header()
} }
body { body {
//Load the fragment and remember all loaded visions //Load the fragment and remember all loaded visions

View File

@ -1,5 +1,5 @@
plugins { plugins {
id("ru.mipt.npm.gradle.mpp") id("space.kscience.gradle.mpp")
} }
kscience{ kscience{
@ -20,9 +20,14 @@ kotlin {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4")
} }
} }
jvmTest{
dependencies{
implementation("ch.qos.logback:logback-classic:1.2.11")
}
}
} }
} }
readme{ readme{
maturity = ru.mipt.npm.gradle.Maturity.DEVELOPMENT maturity = space.kscience.gradle.Maturity.DEVELOPMENT
} }

View File

@ -29,7 +29,7 @@ public class ColorAccessor(
} }
public fun Vision.color(): ReadOnlyProperty<Vision, ColorAccessor> = ReadOnlyProperty { _, property -> public fun Vision.color(): ReadOnlyProperty<Vision, ColorAccessor> = ReadOnlyProperty { _, property ->
ColorAccessor(properties.root(), property.name.asName()) ColorAccessor(properties.root(true), property.name.asName())
} }
public var ColorAccessor?.string: String? public var ColorAccessor?.string: String?

View File

@ -33,7 +33,7 @@ public inline fun MutableVisionContainer<Solid>.composite(
} }
val res = Composite(type, children[0], children[1]) val res = Composite(type, children[0], children[1])
res.properties[Name.EMPTY] = group.properties.raw res.properties.setProperty(Name.EMPTY, group.properties.own)
set(name, res) set(name, res)
return res return res
@ -49,7 +49,7 @@ public fun SolidGroup.smartComposite(
@VisionBuilder builder: SolidGroup.() -> Unit, @VisionBuilder builder: SolidGroup.() -> Unit,
): Solid = if (type == CompositeType.GROUP) { ): Solid = if (type == CompositeType.GROUP) {
val group = SolidGroup(builder) val group = SolidGroup(builder)
if (name == null && group.properties.raw == null) { if (name == null && group.properties.own == null) {
//append directly to group if no properties are defined //append directly to group if no properties are defined
group.items.forEach { (_, value) -> group.items.forEach { (_, value) ->
value.parent = null value.parent = null

View File

@ -107,7 +107,7 @@ public class ExtrudeBuilder(
} }
internal fun build(): Extruded = Extruded(shape, layers).apply { internal fun build(): Extruded = Extruded(shape, layers).apply {
this.properties[Name.EMPTY] = this@ExtrudeBuilder.properties this.properties.setProperty(Name.EMPTY, this@ExtrudeBuilder.properties)
} }
} }

View File

@ -10,7 +10,7 @@ import space.kscience.visionforge.*
public class PolyLine(public val points: List<Point3D>) : SolidBase<PolyLine>() { public class PolyLine(public val points: List<Point3D>) : SolidBase<PolyLine>() {
//var lineType by string() //var lineType by string()
public var thickness: Number by properties[SolidMaterial.MATERIAL_KEY].number { 1.0 } public var thickness: Number by properties.getProperty(SolidMaterial.MATERIAL_KEY).number { 1.0 }
} }
@VisionBuilder @VisionBuilder

View File

@ -170,7 +170,7 @@ internal fun float(name: Name, default: Number): ReadWriteProperty<Solid, Number
internal fun point(name: Name, default: Float): ReadWriteProperty<Solid, Point3D?> = internal fun point(name: Name, default: Float): ReadWriteProperty<Solid, Point3D?> =
object : ReadWriteProperty<Solid, Point3D?> { object : ReadWriteProperty<Solid, Point3D?> {
override fun getValue(thisRef: Solid, property: KProperty<*>): Point3D? { override fun getValue(thisRef: Solid, property: KProperty<*>): Point3D? {
val item = thisRef.properties.raw?.get(name) ?: return null val item = thisRef.properties.own?.get(name) ?: return null
return object : Point3D { return object : Point3D {
override val x: Float get() = item[X_KEY]?.float ?: default override val x: Float get() = item[X_KEY]?.float ?: default
override val y: Float get() = item[Y_KEY]?.float ?: default override val y: Float get() = item[Y_KEY]?.float ?: default
@ -180,7 +180,7 @@ internal fun point(name: Name, default: Float): ReadWriteProperty<Solid, Point3D
override fun setValue(thisRef: Solid, property: KProperty<*>, value: Point3D?) { override fun setValue(thisRef: Solid, property: KProperty<*>, value: Point3D?) {
if (value == null) { if (value == null) {
thisRef.properties[name] = null thisRef.properties.setProperty(name, null)
} else { } else {
thisRef.properties[name + X_KEY] = value.x thisRef.properties[name + X_KEY] = value.x
thisRef.properties[name + Y_KEY] = value.y thisRef.properties[name + Y_KEY] = value.y

View File

@ -54,7 +54,7 @@ public class SolidGroup : AbstractVisionGroup(), Solid, PrototypeHolder, Mutable
* If prototype is a ref, then it is unfolded automatically. * If prototype is a ref, then it is unfolded automatically.
*/ */
override fun getPrototype(name: Name): Solid? = override fun getPrototype(name: Name): Solid? =
prototypes?.get(name)?.unref ?: (parent as? PrototypeHolder)?.getPrototype(name) prototypes?.get(name)?.prototype ?: (parent as? PrototypeHolder)?.getPrototype(name)
/** /**
* Create or edit prototype node as a group * Create or edit prototype node as a group

View File

@ -99,15 +99,15 @@ public class SolidMaterial : Scheme() {
} }
public val Solid.color: ColorAccessor public val Solid.color: ColorAccessor
get() = ColorAccessor(properties.root(), MATERIAL_COLOR_KEY) get() = ColorAccessor(properties.root(true), MATERIAL_COLOR_KEY)
public var Solid.material: SolidMaterial? public var Solid.material: SolidMaterial?
get() = SolidMaterial.read(properties[MATERIAL_KEY]) get() = SolidMaterial.read(properties.getProperty(MATERIAL_KEY))
set(value) = properties.set(MATERIAL_KEY, value?.meta) set(value) = properties.setProperty(MATERIAL_KEY, value?.meta)
@VisionBuilder @VisionBuilder
public fun Solid.material(builder: SolidMaterial.() -> Unit) { public fun Solid.material(builder: SolidMaterial.() -> Unit) {
properties[MATERIAL_KEY].updateWith(SolidMaterial, builder) properties.getProperty(MATERIAL_KEY).updateWith(SolidMaterial, builder)
} }
public var Solid.opacity: Number? public var Solid.opacity: Number?

View File

@ -16,9 +16,10 @@ import space.kscience.visionforge.solid.SolidReference.Companion.REFERENCE_CHILD
* Get a vision prototype if it is a [SolidReference] or vision itself if it is not. * Get a vision prototype if it is a [SolidReference] or vision itself if it is not.
* Unref is recursive, so it always returns a non-reference. * Unref is recursive, so it always returns a non-reference.
*/ */
public val Vision.unref: Solid public val Vision.prototype: Solid
get() = when (this) { get() = when (this) {
is SolidReference -> prototype.unref is SolidReference -> prototype.prototype
is SolidReferenceChild -> prototype.prototype
is Solid -> this is Solid -> this
else -> error("This Vision is neither Solid nor SolidReference") else -> error("This Vision is neither Solid nor SolidReference")
} }
@ -55,13 +56,13 @@ public class SolidReference(
propertiesInternal = value propertiesInternal = value
} }
override val raw: Meta? get() = properties override val own: Meta? get() = properties
override fun get(name: Name, inherit: Boolean, includeStyles: Boolean): MutableMeta { override fun getProperty(name: Name, inherit: Boolean?, includeStyles: Boolean?): MutableMeta {
return properties?.getMeta(name) ?: prototype.properties.get(name, inherit, includeStyles) return properties?.getMeta(name) ?: prototype.properties.getProperty(name, inherit, includeStyles)
} }
override fun getValue(name: Name, inherit: Boolean, includeStyles: Boolean): Value? { override fun getValue(name: Name, inherit: Boolean?, includeStyles: Boolean?): Value? {
return properties?.getValue(name) ?: prototype.properties.getValue(name, inherit, includeStyles) return properties?.getValue(name) ?: prototype.properties.getValue(name, inherit, includeStyles)
} }
} }
@ -105,23 +106,23 @@ internal class SolidReferenceChild(
override val properties: MutableVisionProperties = object : MutableVisionProperties { override val properties: MutableVisionProperties = object : MutableVisionProperties {
override val descriptor: MetaDescriptor get() = this@SolidReferenceChild.descriptor override val descriptor: MetaDescriptor get() = this@SolidReferenceChild.descriptor
override val raw: MutableMeta by lazy { owner.properties[childToken(childName).asName()] } override val own: MutableMeta by lazy { owner.properties.getProperty(childToken(childName).asName()) }
override fun get(name: Name, inherit: Boolean, includeStyles: Boolean): MutableMeta = override fun getProperty(name: Name, inherit: Boolean?, includeStyles: Boolean?): MutableMeta =
raw.getMeta(name) ?: prototype.properties.get(name, inherit, includeStyles) own.getMeta(name) ?: prototype.properties.getProperty(name, inherit, includeStyles)
override fun getValue( override fun getValue(
name: Name, name: Name,
inherit: Boolean, inherit: Boolean?,
includeStyles: Boolean, includeStyles: Boolean?,
): Value? = raw.getValue(name) ?: prototype.properties.getValue(name, inherit, includeStyles) ): Value? = own.getValue(name) ?: prototype.properties.getValue(name, inherit, includeStyles)
override fun set(name: Name, node: Meta?) { override fun setProperty(name: Name, node: Meta?) {
raw.setMeta(name, node) own.setMeta(name, node)
} }
override fun setValue(name: Name, value: Value?) { override fun setValue(name: Name, value: Value?) {
raw.setValue(name, value) own.setValue(name, value)
} }
override val changes: Flow<Name> get() = owner.properties.changes.filter { it.startsWith(childToken(childName)) } override val changes: Flow<Name> get() = owner.properties.changes.filter { it.startsWith(childToken(childName)) }

View File

@ -20,7 +20,7 @@ internal fun Solid.updateFrom(other: Solid): Solid {
scaleX *= other.scaleX scaleX *= other.scaleX
scaleY *= other.scaleY scaleY *= other.scaleY
scaleZ *= other.scaleZ scaleZ *= other.scaleZ
properties[Name.EMPTY] = other.properties.root() properties.setProperty(Name.EMPTY, other.properties.root())
return this return this
} }

View File

@ -3,7 +3,6 @@ package space.kscience.visionforge.solid
import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.withTimeout
import space.kscience.dataforge.meta.int import space.kscience.dataforge.meta.int
import space.kscience.dataforge.meta.string import space.kscience.dataforge.meta.string
import space.kscience.dataforge.names.asName import space.kscience.dataforge.names.asName
@ -26,13 +25,13 @@ class PropertyTest {
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
@Test @Test
fun testColorUpdate() = runTest { fun testColorUpdate() = runTest(dispatchTimeoutMs = 200) {
val box = Box(10.0f, 10.0f, 10.0f) val box = Box(10.0f, 10.0f, 10.0f)
val c = CompletableDeferred<String?>() val c = CompletableDeferred<String?>()
box.onPropertyChange { val subscription = box.onPropertyChange(this) {
if (it == SolidMaterial.MATERIAL_COLOR_KEY) { if (it == SolidMaterial.MATERIAL_COLOR_KEY) {
c.complete(box.color.string) c.complete(box.color.string)
} }
@ -42,8 +41,8 @@ class PropertyTest {
color.set("pink") color.set("pink")
} }
assertEquals("pink", withTimeout(50) { c.await() }) assertEquals("pink", c.await())
subscription.cancel()
} }
@Test @Test

View File

@ -31,7 +31,7 @@ class SerializationTest {
val string = Solids.encodeToString(cube) val string = Solids.encodeToString(cube)
println(string) println(string)
val newCube = Solids.decodeFromString(string) val newCube = Solids.decodeFromString(string)
assertEquals(cube.properties.raw, newCube.properties.raw) assertEquals(cube.properties.own, newCube.properties.own)
} }
@Test @Test
@ -52,7 +52,7 @@ class SerializationTest {
val string = Solids.encodeToString(group) val string = Solids.encodeToString(group)
println(string) println(string)
val reconstructed = Solids.decodeFromString(string) as SolidGroup val reconstructed = Solids.decodeFromString(string) as SolidGroup
assertEquals(group.children["cube"]?.properties?.raw, reconstructed.children["cube"]?.properties?.raw) assertEquals(group.children["cube"]?.properties?.own, reconstructed.children["cube"]?.properties?.own)
} }
@Test @Test

View File

@ -1,5 +1,5 @@
plugins { plugins {
id("ru.mipt.npm.gradle.mpp") id("space.kscience.gradle.mpp")
} }
val tablesVersion = "0.2.0-dev-3" val tablesVersion = "0.2.0-dev-3"
@ -36,5 +36,5 @@ kotlin {
} }
readme{ readme{
maturity = ru.mipt.npm.gradle.Maturity.PROTOTYPE maturity = space.kscience.gradle.Maturity.PROTOTYPE
} }

View File

@ -17,7 +17,7 @@ internal class VisionOfTableTest {
val x by ColumnHeader.typed<Value>() val x by ColumnHeader.typed<Value>()
val y by ColumnHeader.typed<Value>() val y by ColumnHeader.typed<Value>()
val table = ColumnTable(100) { val table = ColumnTable<Value>(100) {
x.fill { it.asValue() } x.fill { it.asValue() }
y.values = x.values.map { it?.double?.pow(2)?.asValue() } y.values = x.values.map { it?.double?.pow(2)?.asValue() }
} }

View File

@ -1,5 +1,5 @@
plugins { plugins {
id("ru.mipt.npm.gradle.js") id("space.kscience.gradle.js")
} }
kotlin{ kotlin{

View File

@ -10,7 +10,6 @@ import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.plus import space.kscience.dataforge.names.plus
import space.kscience.dataforge.names.startsWith import space.kscience.dataforge.names.startsWith
import space.kscience.visionforge.VisionBuilder import space.kscience.visionforge.VisionBuilder
import space.kscience.visionforge.get
import space.kscience.visionforge.onPropertyChange import space.kscience.visionforge.onPropertyChange
import space.kscience.visionforge.set import space.kscience.visionforge.set
import space.kscience.visionforge.solid.Solid import space.kscience.visionforge.solid.Solid
@ -78,7 +77,7 @@ public abstract class MeshThreeFactory<in T : Solid>(
@VisionBuilder @VisionBuilder
public fun Solid.edges(enabled: Boolean = true, block: SolidMaterial.() -> Unit = {}) { public fun Solid.edges(enabled: Boolean = true, block: SolidMaterial.() -> Unit = {}) {
properties.set(EDGES_ENABLED_KEY, enabled) properties.set(EDGES_ENABLED_KEY, enabled)
SolidMaterial.write(properties[EDGES_MATERIAL_KEY]).apply(block) SolidMaterial.write(properties.getProperty(EDGES_MATERIAL_KEY)).apply(block)
} }
internal fun Mesh.applyProperties(obj: Solid): Mesh = apply { internal fun Mesh.applyProperties(obj: Solid): Mesh = apply {
@ -94,9 +93,9 @@ internal fun Mesh.applyProperties(obj: Solid): Mesh = apply {
public 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.properties.get(EDGES_ENABLED_KEY, inherit = true).boolean != false) { if (obj.properties.getProperty(EDGES_ENABLED_KEY, inherit = true).boolean != false) {
val bufferGeometry = geometry as? BufferGeometry ?: return val bufferGeometry = geometry as? BufferGeometry ?: return
val material = ThreeMaterials.getLineMaterial(obj.properties[EDGES_MATERIAL_KEY], true) val material = ThreeMaterials.getLineMaterial(obj.properties.getProperty(EDGES_MATERIAL_KEY), true)
if (edges == null) { if (edges == null) {
add( add(
LineSegments( LineSegments(

View File

@ -11,7 +11,6 @@ import org.w3c.dom.CanvasRenderingContext2D
import org.w3c.dom.CanvasTextBaseline import org.w3c.dom.CanvasTextBaseline
import org.w3c.dom.HTMLCanvasElement import org.w3c.dom.HTMLCanvasElement
import org.w3c.dom.MIDDLE import org.w3c.dom.MIDDLE
import space.kscience.visionforge.get
import space.kscience.visionforge.solid.SolidLabel import space.kscience.visionforge.solid.SolidLabel
import space.kscience.visionforge.solid.SolidMaterial import space.kscience.visionforge.solid.SolidMaterial
import space.kscience.visionforge.solid.three.ThreeCanvas.Companion.DO_NOT_HIGHLIGHT_TAG import space.kscience.visionforge.solid.three.ThreeCanvas.Companion.DO_NOT_HIGHLIGHT_TAG
@ -27,7 +26,7 @@ public object ThreeCanvasLabelFactory : ThreeFactory<SolidLabel> {
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}"
context.fillStyle = obj.properties[SolidMaterial.MATERIAL_COLOR_KEY].value ?: "black" context.fillStyle = obj.properties.getProperty(SolidMaterial.MATERIAL_COLOR_KEY).value ?: "black"
context.textBaseline = CanvasTextBaseline.MIDDLE context.textBaseline = CanvasTextBaseline.MIDDLE
val metrics = context.measureText(obj.text) val metrics = context.measureText(obj.text)
//canvas.width = metrics.width.toInt() //canvas.width = metrics.width.toInt()

View File

@ -4,7 +4,6 @@ import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.Object3D import info.laht.threekt.core.Object3D
import info.laht.threekt.math.Color import info.laht.threekt.math.Color
import info.laht.threekt.objects.LineSegments import info.laht.threekt.objects.LineSegments
import space.kscience.visionforge.get
import space.kscience.visionforge.onPropertyChange import space.kscience.visionforge.onPropertyChange
import space.kscience.visionforge.solid.PolyLine import space.kscience.visionforge.solid.PolyLine
import space.kscience.visionforge.solid.SolidMaterial import space.kscience.visionforge.solid.SolidMaterial
@ -25,7 +24,7 @@ public object ThreeLineFactory : ThreeFactory<PolyLine> {
} }
val material = ThreeMaterials.getLineMaterial( val material = ThreeMaterials.getLineMaterial(
obj.properties[SolidMaterial.MATERIAL_KEY], obj.properties.getProperty(SolidMaterial.MATERIAL_KEY),
false false
) )

View File

@ -10,7 +10,9 @@ import space.kscience.dataforge.meta.*
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.plus import space.kscience.dataforge.names.plus
import space.kscience.visionforge.* import space.kscience.visionforge.Colors
import space.kscience.visionforge.Vision
import space.kscience.visionforge.getStyleNodes
import space.kscience.visionforge.solid.ColorAccessor import space.kscience.visionforge.solid.ColorAccessor
import space.kscience.visionforge.solid.SolidMaterial import space.kscience.visionforge.solid.SolidMaterial
import space.kscience.visionforge.solid.SolidReference import space.kscience.visionforge.solid.SolidReference
@ -129,15 +131,15 @@ private var Material.cached: Boolean
} }
public fun Mesh.updateMaterial(vision: Vision) { public fun Mesh.updateMaterial(vision: Vision) {
val ownMaterialMeta = vision.properties.raw?.get(SolidMaterial.MATERIAL_KEY) val ownMaterialMeta = vision.properties.own?.get(SolidMaterial.MATERIAL_KEY)
if (ownMaterialMeta == null) { if (ownMaterialMeta == null) {
if (vision is SolidReference && vision.getStyleNodes(SolidMaterial.MATERIAL_KEY).isEmpty()) { if (vision is SolidReference && vision.getStyleNodes(SolidMaterial.MATERIAL_KEY).isEmpty()) {
updateMaterial(vision.prototype) updateMaterial(vision.prototype)
} else { } else {
material = ThreeMaterials.cacheMaterial(vision.properties[SolidMaterial.MATERIAL_KEY]) material = ThreeMaterials.cacheMaterial(vision.properties.getProperty(SolidMaterial.MATERIAL_KEY))
} }
} else { } else {
material = ThreeMaterials.buildMaterial(vision.properties[SolidMaterial.MATERIAL_KEY]) material = ThreeMaterials.buildMaterial(vision.properties.getProperty(SolidMaterial.MATERIAL_KEY))
} }
} }
@ -152,15 +154,16 @@ public fun Mesh.updateMaterialProperty(vision: Vision, propertyName: Name) {
} else { } else {
when (propertyName) { when (propertyName) {
SolidMaterial.MATERIAL_COLOR_KEY -> { SolidMaterial.MATERIAL_COLOR_KEY -> {
material.asDynamic().color = vision.properties[SolidMaterial.MATERIAL_COLOR_KEY].threeColor() material.asDynamic().color = vision.properties.getProperty(SolidMaterial.MATERIAL_COLOR_KEY).threeColor()
?: ThreeMaterials.DEFAULT_COLOR ?: ThreeMaterials.DEFAULT_COLOR
} }
SolidMaterial.SPECULAR_COLOR_KEY -> { SolidMaterial.SPECULAR_COLOR_KEY -> {
material.asDynamic().specular = vision.properties[SolidMaterial.SPECULAR_COLOR_KEY].threeColor() material.asDynamic().specular = vision.properties.getProperty(SolidMaterial.SPECULAR_COLOR_KEY).threeColor()
?: ThreeMaterials.DEFAULT_COLOR ?: ThreeMaterials.DEFAULT_COLOR
} }
SolidMaterial.MATERIAL_EMISSIVE_COLOR_KEY -> { SolidMaterial.MATERIAL_EMISSIVE_COLOR_KEY -> {
material.asDynamic().emissive = vision.properties[SolidMaterial.MATERIAL_EMISSIVE_COLOR_KEY].threeColor() material.asDynamic().emissive = vision.properties.getProperty(SolidMaterial.MATERIAL_EMISSIVE_COLOR_KEY)
.threeColor()
?: ThreeMaterials.BLACK_COLOR ?: ThreeMaterials.BLACK_COLOR
} }
SolidMaterial.MATERIAL_OPACITY_KEY -> { SolidMaterial.MATERIAL_OPACITY_KEY -> {

View File

@ -1,6 +1,8 @@
package space.kscience.visionforge.solid.three package space.kscience.visionforge.solid.three
import info.laht.threekt.core.Object3D import info.laht.threekt.core.Object3D
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 space.kscience.dataforge.context.* import space.kscience.dataforge.context.*
@ -9,7 +11,6 @@ import space.kscience.dataforge.meta.update
import space.kscience.dataforge.names.* import space.kscience.dataforge.names.*
import space.kscience.visionforge.ElementVisionRenderer import space.kscience.visionforge.ElementVisionRenderer
import space.kscience.visionforge.Vision import space.kscience.visionforge.Vision
import space.kscience.visionforge.onPropertyChange
import space.kscience.visionforge.solid.* import space.kscience.visionforge.solid.*
import space.kscience.visionforge.solid.specifications.Canvas3DOptions import space.kscience.visionforge.solid.specifications.Canvas3DOptions
import space.kscience.visionforge.visible import space.kscience.visionforge.visible
@ -48,11 +49,11 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
public fun buildObject3D(obj: Solid): Object3D = when (obj) { public fun buildObject3D(obj: Solid): Object3D = when (obj) {
is ThreeJsVision -> obj.render(this) is ThreeJsVision -> obj.render(this)
is SolidReferenceGroup -> ThreeReferenceFactory.build(this, obj) is SolidReference -> ThreeReferenceFactory.build(this, obj)
is SolidGroup -> { is SolidGroup -> {
val group = ThreeGroup() val group = ThreeGroup()
obj.items.forEach { (token, child) -> obj.items.forEach { (token, child) ->
if (child is Solid && token != SolidGroup.PROTOTYPES_TOKEN && child.ignore != true) { if (token != SolidGroup.PROTOTYPES_TOKEN && child.ignore != true) {
try { try {
val object3D = buildObject3D(child) val object3D = buildObject3D(child)
group[token] = object3D group[token] = object3D
@ -67,7 +68,7 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
updatePosition(obj) updatePosition(obj)
//obj.onChildrenChange() //obj.onChildrenChange()
obj.onPropertyChange { name -> obj.properties.changes.onEach { name ->
if ( if (
name.startsWith(Solid.POSITION_KEY) || name.startsWith(Solid.POSITION_KEY) ||
name.startsWith(Solid.ROTATION_KEY) || name.startsWith(Solid.ROTATION_KEY) ||
@ -78,10 +79,10 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
} else if (name == Vision.VISIBLE_KEY) { } else if (name == Vision.VISIBLE_KEY) {
visible = obj.visible ?: true visible = obj.visible ?: true
} }
} }.launchIn(context)
obj.onStructureChanged(this){ childName -> obj.children.changes.onEach { childName ->
val child = get(childName) val child = obj.children[childName]
//removing old object //removing old object
findChild(childName)?.let { oldChild -> findChild(childName)?.let { oldChild ->
@ -97,7 +98,7 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
logger.error(ex) { "Failed to render $child" } logger.error(ex) { "Failed to render $child" }
} }
} }
} }.launchIn(context)
} }
} }
is Composite -> compositeFactory.build(this, obj) is Composite -> compositeFactory.build(this, obj)

View File

@ -7,20 +7,21 @@ import space.kscience.dataforge.names.cutFirst
import space.kscience.dataforge.names.firstOrNull import space.kscience.dataforge.names.firstOrNull
import space.kscience.visionforge.onPropertyChange import space.kscience.visionforge.onPropertyChange
import space.kscience.visionforge.solid.Solid import space.kscience.visionforge.solid.Solid
import space.kscience.visionforge.solid.SolidReferenceGroup import space.kscience.visionforge.solid.SolidReference
import space.kscience.visionforge.solid.SolidReferenceGroup.Companion.REFERENCE_CHILD_PROPERTY_PREFIX import space.kscience.visionforge.solid.SolidReference.Companion.REFERENCE_CHILD_PROPERTY_PREFIX
import kotlin.reflect.KClass import kotlin.reflect.KClass
public object ThreeReferenceFactory : ThreeFactory<SolidReferenceGroup> { public object ThreeReferenceFactory : ThreeFactory<SolidReference> {
private val cache = HashMap<Solid, Object3D>() private val cache = HashMap<Solid, Object3D>()
override val type: KClass<SolidReferenceGroup> = SolidReferenceGroup::class override val type: KClass<SolidReference> = SolidReference::class
private fun Object3D.replicate(): Object3D { private fun Object3D.replicate(): Object3D {
return when (this) { return when (this) {
is Mesh -> Mesh(geometry, material).also { is Mesh -> Mesh(geometry, material).also {
it.applyMatrix4(matrix) it.applyMatrix4(matrix)
} }
else -> clone(false) else -> clone(false)
}.also { obj: Object3D -> }.also { obj: Object3D ->
obj.name = this.name obj.name = this.name
@ -30,7 +31,7 @@ public object ThreeReferenceFactory : ThreeFactory<SolidReferenceGroup> {
} }
} }
override fun build(three: ThreePlugin, obj: SolidReferenceGroup): Object3D { override fun build(three: ThreePlugin, obj: SolidReference): 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)
@ -39,18 +40,20 @@ public object ThreeReferenceFactory : ThreeFactory<SolidReferenceGroup> {
val object3D: Object3D = cachedObject.replicate() val object3D: Object3D = cachedObject.replicate()
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.material = ThreeMaterials.buildMaterial(obj.getProperty(SolidMaterial.MATERIAL_KEY).node!!)
object3D.applyProperties(obj) object3D.applyProperties(obj)
} }
//TODO apply child properties //TODO apply child properties
obj.onPropertyChange { name-> obj.onPropertyChange { name ->
if (name.firstOrNull()?.body == REFERENCE_CHILD_PROPERTY_PREFIX) { if (name.firstOrNull()?.body == REFERENCE_CHILD_PROPERTY_PREFIX) {
val childName = name.firstOrNull()?.index?.let(Name::parse) ?: error("Wrong syntax for reference child property: '$name'") val childName = name.firstOrNull()?.index?.let(Name::parse)
?: error("Wrong syntax for reference child property: '$name'")
val propertyName = name.cutFirst() val propertyName = name.cutFirst()
val referenceChild = obj.children[childName] ?: error("Reference child with name '$childName' not found") val referenceChild =
obj.children[childName] ?: error("Reference child with name '$childName' not found")
val child = object3D.findChild(childName) ?: error("Object child with name '$childName' not found") val child = object3D.findChild(childName) ?: error("Object child with name '$childName' not found")
child.updateProperty(referenceChild, propertyName) child.updateProperty(referenceChild, propertyName)
} else { } else {

View File

@ -1,5 +1,5 @@
plugins { plugins {
id("ru.mipt.npm.gradle.mpp") id("space.kscience.gradle.mpp")
} }
val ktorVersion: String by rootProject.extra val ktorVersion: String by rootProject.extra