forked from kscience/visionforge
Another huge refactoring of property updates
This commit is contained in:
parent
4c235b0f53
commit
0259d4eb15
@ -42,7 +42,7 @@ fun main() {
|
|||||||
val targetVision = sat[target] as Solid
|
val targetVision = sat[target] as Solid
|
||||||
targetVision.color("red")
|
targetVision.color("red")
|
||||||
delay(300)
|
delay(300)
|
||||||
targetVision.color("green")
|
targetVision.color("darkgreen")
|
||||||
delay(10)
|
delay(10)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,6 @@ 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
|
||||||
import info.laht.threekt.objects.Mesh
|
import info.laht.threekt.objects.Mesh
|
||||||
import kotlinx.coroutines.flow.launchIn
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
|
||||||
internal fun SolidGroup.varBox(
|
internal fun SolidGroup.varBox(
|
||||||
@ -62,7 +60,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
|
||||||
propertyNameFlow.onEach { 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
|
||||||
@ -78,7 +76,8 @@ internal class VariableBox(xSize: Number, ySize: Number, zSize: Number) : ThreeV
|
|||||||
}
|
}
|
||||||
else -> mesh.updateProperty(this@VariableBox, name)
|
else -> mesh.updateProperty(this@VariableBox, name)
|
||||||
}
|
}
|
||||||
}.launchIn(three.context)
|
}
|
||||||
|
|
||||||
return mesh
|
return mesh
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,11 +13,12 @@ public fun RBuilder.visionPropertyEditor(
|
|||||||
descriptor: NodeDescriptor? = vision.descriptor,
|
descriptor: NodeDescriptor? = vision.descriptor,
|
||||||
key: Any? = null,
|
key: Any? = null,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
card("Properties") {
|
card("Properties") {
|
||||||
propertyEditor(
|
propertyEditor(
|
||||||
provider = vision.ownProperties,
|
provider = vision.ownProperties,
|
||||||
defaultProvider = vision.allProperties(),
|
defaultProvider = vision.allProperties(),
|
||||||
updateFlow = vision.propertyNameFlow,
|
updateFlow = vision.propertyChanges,
|
||||||
descriptor = descriptor,
|
descriptor = descriptor,
|
||||||
key = key)
|
key = key)
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ 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.CoroutineScope
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.channels.awaitClose
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
@ -66,6 +67,9 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
|
|||||||
val itemName by useState { props.name ?: Name.EMPTY }
|
val itemName by useState { props.name ?: Name.EMPTY }
|
||||||
var item: MetaItem<*>? by useState { props.provider.getItem(itemName) }
|
var item: MetaItem<*>? by useState { props.provider.getItem(itemName) }
|
||||||
val descriptorItem: ItemDescriptor? = props.descriptor?.get(itemName)
|
val descriptorItem: ItemDescriptor? = props.descriptor?.get(itemName)
|
||||||
|
|
||||||
|
if(descriptorItem?.hidden == true) return //fail fast for hidden property
|
||||||
|
|
||||||
var actualItem: MetaItem<Meta>? by useState {
|
var actualItem: MetaItem<Meta>? by useState {
|
||||||
item ?: props.defaultProvider?.getItem(itemName) ?: descriptorItem?.defaultItem()
|
item ?: props.defaultProvider?.getItem(itemName) ?: descriptorItem?.defaultItem()
|
||||||
}
|
}
|
||||||
@ -106,8 +110,6 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
|
|||||||
update()
|
update()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (actualItem is MetaItem.NodeItem) {
|
if (actualItem is MetaItem.NodeItem) {
|
||||||
styledDiv {
|
styledDiv {
|
||||||
css {
|
css {
|
||||||
|
@ -5,6 +5,7 @@ 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
|
||||||
@ -54,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?.notifyPropertyChanged(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) {
|
||||||
|
@ -9,9 +9,10 @@ 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.vision.Vision.Companion.TYPE
|
import hep.dataforge.vision.Vision.Companion.TYPE
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.callbackFlow
|
||||||
import kotlinx.serialization.Transient
|
import kotlinx.serialization.Transient
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -29,7 +30,7 @@ public interface Vision : Described {
|
|||||||
/**
|
/**
|
||||||
* A coroutine scope for asynchronous calls and locks
|
* A coroutine scope for asynchronous calls and locks
|
||||||
*/
|
*/
|
||||||
public val scope: CoroutineScope get() = parent?.scope?: GlobalScope
|
public val scope: CoroutineScope get() = parent?.scope ?: GlobalScope
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A fast accessor method to get own property (no inheritance or styles).
|
* A fast accessor method to get own property (no inheritance or styles).
|
||||||
@ -55,17 +56,26 @@ public interface Vision : Described {
|
|||||||
*/
|
*/
|
||||||
public fun setProperty(name: Name, item: MetaItem<*>?, notify: Boolean = true)
|
public fun setProperty(name: Name, item: MetaItem<*>?, notify: Boolean = true)
|
||||||
|
|
||||||
|
public fun onPropertyChange(scope: CoroutineScope, callback: suspend (Name) -> Unit)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flow of property invalidation events. It does not contain property values after invalidation since it is not clear
|
* 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.
|
* if it should include inherited properties etc.
|
||||||
*/
|
*/
|
||||||
public val propertyNameFlow: Flow<Name>
|
public val propertyChanges: Flow<Name> get() = callbackFlow<Name> {
|
||||||
|
coroutineScope {
|
||||||
|
onPropertyChange(this) {
|
||||||
|
send(it)
|
||||||
|
}
|
||||||
|
awaitClose { cancel() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notify all listeners that a property has been changed and should be invalidated
|
* Notify all listeners that a property has been changed and should be invalidated
|
||||||
*/
|
*/
|
||||||
public fun notifyPropertyChanged(propertyName: Name): Unit
|
public suspend fun notifyPropertyChanged(propertyName: Name): Unit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update this vision using external meta. Children are not updated.
|
* Update this vision using external meta. Children are not updated.
|
||||||
@ -82,6 +92,12 @@ public interface Vision : Described {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public fun Vision.asyncNotifyPropertyChange(propertyName: Name){
|
||||||
|
scope.launch {
|
||||||
|
notifyPropertyChanged(propertyName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Own properties, excluding inheritance, styles and descriptor
|
* Own properties, excluding inheritance, styles and descriptor
|
||||||
*/
|
*/
|
||||||
|
@ -11,8 +11,11 @@ import hep.dataforge.names.Name
|
|||||||
import hep.dataforge.names.asName
|
import hep.dataforge.names.asName
|
||||||
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.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.SharedFlow
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
@ -44,7 +47,9 @@ public open class VisionBase : Vision {
|
|||||||
properties = newProperties
|
properties = newProperties
|
||||||
newProperties.onChange(this) { name, oldItem, newItem ->
|
newProperties.onChange(this) { name, oldItem, newItem ->
|
||||||
if (oldItem != newItem) {
|
if (oldItem != newItem) {
|
||||||
notifyPropertyChanged(name)
|
scope.launch {
|
||||||
|
notifyPropertyChanged(name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -77,14 +82,16 @@ public open class VisionBase : Vision {
|
|||||||
@Synchronized
|
@Synchronized
|
||||||
override fun setProperty(name: Name, item: MetaItem<*>?, notify: Boolean) {
|
override fun setProperty(name: Name, item: MetaItem<*>?, notify: Boolean) {
|
||||||
getOrCreateConfig().setItem(name, item)
|
getOrCreateConfig().setItem(name, item)
|
||||||
if(notify) {
|
if (notify) {
|
||||||
notifyPropertyChanged(name)
|
scope.launch {
|
||||||
|
notifyPropertyChanged(name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override val descriptor: NodeDescriptor? get() = null
|
override val descriptor: NodeDescriptor? get() = null
|
||||||
|
|
||||||
private fun updateStyles(names: List<String>) {
|
private suspend fun updateStyles(names: List<String>) {
|
||||||
names.mapNotNull { getStyle(it) }.asSequence()
|
names.mapNotNull { getStyle(it) }.asSequence()
|
||||||
.flatMap { it.items.asSequence() }
|
.flatMap { it.items.asSequence() }
|
||||||
.distinctBy { it.key }
|
.distinctBy { it.key }
|
||||||
@ -94,17 +101,19 @@ public open class VisionBase : Vision {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Transient
|
@Transient
|
||||||
private val _propertyInvalidationFlow: MutableSharedFlow<Name> = MutableSharedFlow()
|
private val propertyInvalidationFlow: MutableSharedFlow<Name> = MutableSharedFlow()
|
||||||
|
|
||||||
override val propertyNameFlow: SharedFlow<Name> get() = _propertyInvalidationFlow
|
override val propertyChanges: Flow<Name> get() = propertyInvalidationFlow
|
||||||
|
|
||||||
override fun notifyPropertyChanged(propertyName: Name) {
|
override fun onPropertyChange(scope: CoroutineScope, callback: suspend (Name) -> Unit) {
|
||||||
|
propertyInvalidationFlow.onEach(callback).launchIn(scope)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun notifyPropertyChanged(propertyName: Name) {
|
||||||
if (propertyName == STYLE_KEY) {
|
if (propertyName == STYLE_KEY) {
|
||||||
updateStyles(styles)
|
updateStyles(styles)
|
||||||
}
|
}
|
||||||
scope.launch {
|
propertyInvalidationFlow.emit(propertyName)
|
||||||
_propertyInvalidationFlow.emit(propertyName)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun configure(block: MutableMeta<*>.() -> Unit) {
|
public fun configure(block: MutableMeta<*>.() -> Unit) {
|
||||||
|
@ -67,6 +67,9 @@ public class VisionChange(
|
|||||||
@Serializable(MetaSerializer::class) public val properties: Meta? = null,
|
@Serializable(MetaSerializer::class) public val properties: Meta? = null,
|
||||||
public val children: Map<Name, VisionChange>? = null,
|
public val children: Map<Name, VisionChange>? = null,
|
||||||
) {
|
) {
|
||||||
|
init {
|
||||||
|
(vision as? VisionGroup)?.attachChildren()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,10 +84,10 @@ private fun CoroutineScope.collectChange(
|
|||||||
) {
|
) {
|
||||||
|
|
||||||
//Collect properties change
|
//Collect properties change
|
||||||
source.propertyNameFlow.onEach { propertyName ->
|
source.onPropertyChange(this) { propertyName ->
|
||||||
val newItem = source.getProperty(propertyName, inherit = false, includeStyles = false, includeDefaults = false)
|
val newItem = source.getProperty(propertyName, inherit = false, includeStyles = false, includeDefaults = false)
|
||||||
collector().propertyChanged(name, propertyName, newItem)
|
collector().propertyChanged(name, propertyName, newItem)
|
||||||
}.launchIn(this)
|
}
|
||||||
|
|
||||||
if (source is VisionGroup) {
|
if (source is VisionGroup) {
|
||||||
//Subscribe for children changes
|
//Subscribe for children changes
|
||||||
|
@ -28,7 +28,7 @@ public open class VisionGroupBase : VisionBase(), MutableVisionGroup {
|
|||||||
*/
|
*/
|
||||||
override val children: Map<NameToken, Vision> get() = childrenInternal
|
override val children: Map<NameToken, Vision> get() = childrenInternal
|
||||||
|
|
||||||
override fun notifyPropertyChanged(propertyName: Name) {
|
override suspend fun notifyPropertyChanged(propertyName: Name) {
|
||||||
super.notifyPropertyChanged(propertyName)
|
super.notifyPropertyChanged(propertyName)
|
||||||
for (obj in this) {
|
for (obj in this) {
|
||||||
obj.notifyPropertyChanged(propertyName)
|
obj.notifyPropertyChanged(propertyName)
|
||||||
|
@ -1,15 +1,11 @@
|
|||||||
package hep.dataforge.vision
|
package hep.dataforge.vision
|
||||||
|
|
||||||
import hep.dataforge.meta.*
|
import hep.dataforge.meta.*
|
||||||
import hep.dataforge.meta.descriptors.NodeDescriptor
|
|
||||||
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<*>? {
|
public fun Sequence<MetaItem<*>?>.merge(): MetaItem<*>? {
|
||||||
return when (val first = firstOrNull { it != null }) {
|
return when (val first = firstOrNull { it != null }) {
|
||||||
null -> null
|
null -> null
|
||||||
@ -22,14 +18,6 @@ public fun Sequence<MetaItem<*>?>.merge(): MetaItem<*>? {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public inline fun <reified E : Enum<E>> NodeDescriptor.enum(key: Name, default: E?): Unit = value(key) {
|
|
||||||
type(ValueType.STRING)
|
|
||||||
default?.let {
|
|
||||||
default(default)
|
|
||||||
}
|
|
||||||
allowedValues = enumValues<E>().map { it.asValue() }
|
|
||||||
}
|
|
||||||
|
|
||||||
@DFExperimental
|
@DFExperimental
|
||||||
public val Vision.properties: Config?
|
public val Vision.properties: Config?
|
||||||
get() = (this as? VisionBase)?.properties
|
get() = (this as? VisionBase)?.properties
|
||||||
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,11 +1,13 @@
|
|||||||
package hep.dataforge.vision
|
package hep.dataforge.vision
|
||||||
|
|
||||||
import hep.dataforge.meta.Meta
|
import hep.dataforge.meta.*
|
||||||
import hep.dataforge.meta.boolean
|
|
||||||
import hep.dataforge.meta.descriptors.ItemDescriptor
|
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.meta.descriptors.attributes
|
||||||
import hep.dataforge.meta.get
|
import hep.dataforge.names.Name
|
||||||
import hep.dataforge.meta.set
|
import hep.dataforge.values.ValueType
|
||||||
|
import hep.dataforge.values.asValue
|
||||||
|
|
||||||
private const val INHERITED_DESCRIPTOR_ATTRIBUTE = "inherited"
|
private const val INHERITED_DESCRIPTOR_ATTRIBUTE = "inherited"
|
||||||
private const val STYLE_DESCRIPTOR_ATTRIBUTE = "useStyles"
|
private const val STYLE_DESCRIPTOR_ATTRIBUTE = "useStyles"
|
||||||
@ -30,3 +32,48 @@ public val Vision.describedProperties: Meta
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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()
|
||||||
|
}
|
@ -18,9 +18,6 @@ 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.HashMap
|
|
||||||
import kotlin.collections.forEach
|
|
||||||
import kotlin.collections.maxByOrNull
|
|
||||||
import kotlin.collections.set
|
import kotlin.collections.set
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
@ -84,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" }
|
||||||
@ -102,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)
|
||||||
}
|
}
|
||||||
@ -122,15 +129,20 @@ 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){
|
||||||
|
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")
|
?: 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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onopen = {
|
onopen = {
|
||||||
|
@ -4,8 +4,6 @@ import hep.dataforge.names.*
|
|||||||
import hep.dataforge.vision.Vision
|
import hep.dataforge.vision.Vision
|
||||||
import javafx.scene.Group
|
import javafx.scene.Group
|
||||||
import javafx.scene.Node
|
import javafx.scene.Node
|
||||||
import kotlinx.coroutines.flow.launchIn
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
class FXReferenceFactory(val plugin: FX3DPlugin) : FX3DFactory<SolidReferenceGroup> {
|
class FXReferenceFactory(val plugin: FX3DPlugin) : FX3DFactory<SolidReferenceGroup> {
|
||||||
@ -15,7 +13,7 @@ class FXReferenceFactory(val plugin: FX3DPlugin) : FX3DFactory<SolidReferenceGro
|
|||||||
val prototype = obj.prototype
|
val prototype = obj.prototype
|
||||||
val node = plugin.buildNode(prototype)
|
val node = plugin.buildNode(prototype)
|
||||||
|
|
||||||
obj.propertyNameFlow.onEach { name->
|
obj.onPropertyChange(plugin.context) { name->
|
||||||
if (name.firstOrNull()?.body == SolidReferenceGroup.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()
|
||||||
@ -23,7 +21,7 @@ class FXReferenceFactory(val plugin: FX3DPlugin) : FX3DFactory<SolidReferenceGro
|
|||||||
val child = node.findChild(childName) ?: error("Object child with name '$childName' not found")
|
val child = node.findChild(childName) ?: error("Object child with name '$childName' not found")
|
||||||
child.updateProperty(referenceChild, propertyName)
|
child.updateProperty(referenceChild, propertyName)
|
||||||
}
|
}
|
||||||
}.launchIn(plugin.context)
|
}
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,6 @@ import hep.dataforge.names.toName
|
|||||||
import hep.dataforge.vision.Vision
|
import hep.dataforge.vision.Vision
|
||||||
import javafx.application.Platform
|
import javafx.application.Platform
|
||||||
import javafx.beans.binding.ObjectBinding
|
import javafx.beans.binding.ObjectBinding
|
||||||
import kotlinx.coroutines.flow.launchIn
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
|
||||||
import tornadofx.*
|
import tornadofx.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -18,7 +16,7 @@ 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.propertyNameFlow.onEach { 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()
|
||||||
@ -30,7 +28,7 @@ class VisualObjectFXBinding(val fx: FX3DPlugin, val obj: Vision) {
|
|||||||
// bindings[currentName]?.invalidate()
|
// bindings[currentName]?.invalidate()
|
||||||
// currentName = currentName.cutLast()
|
// currentName = currentName.cutLast()
|
||||||
// }
|
// }
|
||||||
}.launchIn(fx.context)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun get(key: Name): ObjectBinding<MetaItem<*>?> {
|
operator fun get(key: Name): ObjectBinding<MetaItem<*>?> {
|
||||||
|
@ -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,19 @@ 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) {
|
||||||
|
|
||||||
|
val initialVision = VisionChange(vision = vision)
|
||||||
|
val initialJson = visionManager.jsonFormat.encodeToString(
|
||||||
|
VisionChange.serializer(),
|
||||||
|
initialVision
|
||||||
|
)
|
||||||
|
outgoing.send(Frame.Text(initialJson))
|
||||||
|
|
||||||
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
|
||||||
)
|
)
|
||||||
|
@ -61,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)
|
||||||
}
|
}
|
||||||
@ -69,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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,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()
|
||||||
notifyPropertyChanged(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()
|
||||||
notifyPropertyChanged(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()
|
||||||
notifyPropertyChanged(Solid.Z_POSITION_KEY)
|
asyncNotifyPropertyChange(Solid.Z_POSITION_KEY)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Solid.rotation(): Point3D =
|
private fun Solid.rotation(): Point3D =
|
||||||
@ -176,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()
|
||||||
notifyPropertyChanged(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()
|
||||||
notifyPropertyChanged(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()
|
||||||
notifyPropertyChanged(Solid.Z_ROTATION_KEY)
|
asyncNotifyPropertyChange(Solid.Z_ROTATION_KEY)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Solid.scale(): Point3D =
|
private fun Solid.scale(): Point3D =
|
||||||
@ -200,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()
|
||||||
notifyPropertyChanged(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()
|
||||||
notifyPropertyChanged(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()
|
||||||
notifyPropertyChanged(Solid.Z_SCALE_KEY)
|
asyncNotifyPropertyChange(Solid.Z_SCALE_KEY)
|
||||||
}
|
}
|
@ -42,7 +42,7 @@ public class SolidGroup : VisionGroupBase(), Solid, PrototypeHolder {
|
|||||||
* Ger a prototype redirecting the request to the parent if prototype is not found
|
* Ger a prototype redirecting the request to the parent if prototype is not found
|
||||||
*/
|
*/
|
||||||
override fun getPrototype(name: Name): Solid? =
|
override fun getPrototype(name: Name): Solid? =
|
||||||
prototypes?.get(name) as? Solid ?: (parent as? PrototypeHolder)?.getPrototype(name)
|
(prototypes?.get(name) as? Solid) ?: (parent as? PrototypeHolder)?.getPrototype(name)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create or edit prototype node as a group
|
* Create or edit prototype node as a group
|
||||||
@ -108,7 +108,7 @@ 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() {
|
) : VisionGroupBase(), PrototypeHolder {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
childrenInternal.putAll(children)
|
childrenInternal.putAll(children)
|
||||||
@ -155,4 +155,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
|
||||||
}
|
}
|
||||||
|
@ -89,18 +89,24 @@ 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
|
inherited = true
|
||||||
usesStyles = true
|
usesStyles = true
|
||||||
type(ValueType.STRING, ValueType.NUMBER)
|
type(ValueType.STRING, ValueType.NUMBER)
|
||||||
widgetType = "color"
|
widgetType = "color"
|
||||||
}
|
}
|
||||||
// value(SPECULAR_COLOR_KEY) {
|
|
||||||
// inherited = true
|
value(SPECULAR_COLOR_KEY) {
|
||||||
// usesStyles = true
|
inherited = true
|
||||||
// type(ValueType.STRING, ValueType.NUMBER)
|
usesStyles = true
|
||||||
// widgetType = "color"
|
type(ValueType.STRING, ValueType.NUMBER)
|
||||||
// }
|
widgetType = "color"
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
|
||||||
value(OPACITY_KEY) {
|
value(OPACITY_KEY) {
|
||||||
inherited = true
|
inherited = true
|
||||||
usesStyles = true
|
usesStyles = true
|
||||||
|
@ -5,9 +5,7 @@ import hep.dataforge.meta.descriptors.NodeDescriptor
|
|||||||
import hep.dataforge.meta.descriptors.get
|
import hep.dataforge.meta.descriptors.get
|
||||||
import hep.dataforge.names.*
|
import hep.dataforge.names.*
|
||||||
import hep.dataforge.vision.*
|
import hep.dataforge.vision.*
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.filter
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@ -128,14 +126,15 @@ public class SolidReferenceGroup(
|
|||||||
error("Setting a parent for a reference child is not possible")
|
error("Setting a parent for a reference child is not possible")
|
||||||
}
|
}
|
||||||
|
|
||||||
override val propertyNameFlow: Flow<Name>
|
override fun onPropertyChange(scope: CoroutineScope, callback: suspend (Name) -> Unit) {
|
||||||
get() = this@SolidReferenceGroup.propertyNameFlow.filter { name ->
|
this@SolidReferenceGroup.onPropertyChange(scope) { name ->
|
||||||
name.startsWith(childToken(childName))
|
if (name.startsWith(childToken(childName))) {
|
||||||
}.map { name ->
|
callback(name.cutFirst())
|
||||||
name.cutFirst()
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun notifyPropertyChanged(propertyName: Name) {
|
override suspend fun notifyPropertyChanged(propertyName: Name) {
|
||||||
this@SolidReferenceGroup.notifyPropertyChanged(childPropertyName(childName, propertyName))
|
this@SolidReferenceGroup.notifyPropertyChanged(childPropertyName(childName, propertyName))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,8 +15,6 @@ import info.laht.threekt.geometries.EdgesGeometry
|
|||||||
import info.laht.threekt.geometries.WireframeGeometry
|
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 kotlinx.coroutines.flow.launchIn
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -45,7 +43,7 @@ public abstract class MeshThreeFactory<in T : Solid>(
|
|||||||
}.applyProperties(obj)
|
}.applyProperties(obj)
|
||||||
|
|
||||||
//add listener to object properties
|
//add listener to object properties
|
||||||
obj.propertyNameFlow.onEach { 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
|
||||||
@ -59,7 +57,7 @@ public abstract class MeshThreeFactory<in T : Solid>(
|
|||||||
name.startsWith(EDGES_KEY) -> mesh.applyEdges(obj)
|
name.startsWith(EDGES_KEY) -> mesh.applyEdges(obj)
|
||||||
else -> mesh.updateProperty(obj, name)
|
else -> mesh.updateProperty(obj, name)
|
||||||
}
|
}
|
||||||
}.launchIn(three.updateScope)
|
}
|
||||||
|
|
||||||
return mesh
|
return mesh
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,7 @@ 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)
|
this.material = getMaterial(source, true)
|
||||||
} else if (
|
} else if (
|
||||||
propertyName.startsWith(Solid.POSITION_KEY)
|
propertyName.startsWith(Solid.POSITION_KEY)
|
||||||
|| propertyName.startsWith(Solid.ROTATION)
|
|| propertyName.startsWith(Solid.ROTATION)
|
||||||
|
@ -8,8 +8,6 @@ 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
|
||||||
import kotlinext.js.jsObject
|
import kotlinext.js.jsObject
|
||||||
import kotlinx.coroutines.flow.launchIn
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -27,10 +25,10 @@ public object ThreeLabelFactory : ThreeFactory<SolidLabel> {
|
|||||||
})
|
})
|
||||||
return Mesh(textGeo, getMaterial(obj, true)).apply {
|
return Mesh(textGeo, getMaterial(obj, true)).apply {
|
||||||
updatePosition(obj)
|
updatePosition(obj)
|
||||||
obj.propertyNameFlow.onEach { _ ->
|
obj.onPropertyChange(three.updateScope){ _ ->
|
||||||
//TODO
|
//TODO
|
||||||
three.logger.warn{"Label parameter change not implemented"}
|
three.logger.warn{"Label parameter change not implemented"}
|
||||||
}.launchIn(three.updateScope)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -9,8 +9,6 @@ import info.laht.threekt.core.Geometry
|
|||||||
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 kotlinx.coroutines.flow.launchIn
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
public object ThreeLineFactory : ThreeFactory<PolyLine> {
|
public object ThreeLineFactory : ThreeFactory<PolyLine> {
|
||||||
@ -30,9 +28,9 @@ 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.propertyNameFlow.onEach { propertyName ->
|
obj.onPropertyChange(three.updateScope) { propertyName ->
|
||||||
updateProperty(obj, propertyName)
|
updateProperty(obj, propertyName)
|
||||||
}.launchIn(three.updateScope)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,6 +59,10 @@ public object ThreeMaterials {
|
|||||||
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
|
||||||
|
@ -67,7 +67,7 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
|
|||||||
updatePosition(obj)
|
updatePosition(obj)
|
||||||
//obj.onChildrenChange()
|
//obj.onChildrenChange()
|
||||||
|
|
||||||
obj.propertyNameFlow.onEach { name ->
|
obj.onPropertyChange(updateScope) { name ->
|
||||||
if (
|
if (
|
||||||
name.startsWith(Solid.POSITION_KEY) ||
|
name.startsWith(Solid.POSITION_KEY) ||
|
||||||
name.startsWith(Solid.ROTATION) ||
|
name.startsWith(Solid.ROTATION) ||
|
||||||
@ -78,7 +78,7 @@ 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(updateScope)
|
}
|
||||||
|
|
||||||
obj.structureChanges.onEach { (nameToken, _, child) ->
|
obj.structureChanges.onEach { (nameToken, _, child) ->
|
||||||
// if (name.isEmpty()) {
|
// if (name.isEmpty()) {
|
||||||
|
@ -9,8 +9,6 @@ import hep.dataforge.vision.solid.SolidReferenceGroup.Companion.REFERENCE_CHILD_
|
|||||||
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 kotlinx.coroutines.flow.launchIn
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
public object ThreeReferenceFactory : ThreeFactory<SolidReferenceGroup> {
|
public object ThreeReferenceFactory : ThreeFactory<SolidReferenceGroup> {
|
||||||
@ -47,7 +45,7 @@ public object ThreeReferenceFactory : ThreeFactory<SolidReferenceGroup> {
|
|||||||
|
|
||||||
//TODO apply child properties
|
//TODO apply child properties
|
||||||
|
|
||||||
obj.propertyNameFlow.onEach { name->
|
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()
|
||||||
@ -57,7 +55,7 @@ public object ThreeReferenceFactory : ThreeFactory<SolidReferenceGroup> {
|
|||||||
} else {
|
} else {
|
||||||
object3D.updateProperty(obj, name)
|
object3D.updateProperty(obj, name)
|
||||||
}
|
}
|
||||||
}.launchIn(three.updateScope)
|
}
|
||||||
|
|
||||||
|
|
||||||
return object3D
|
return object3D
|
||||||
|
Loading…
Reference in New Issue
Block a user