Another huge refactoring of property updates

This commit is contained in:
Alexander Nozik 2020-12-20 21:43:29 +03:00
parent 4c235b0f53
commit 0259d4eb15
27 changed files with 238 additions and 148 deletions

View File

@ -42,7 +42,7 @@ fun main() {
val targetVision = sat[target] as Solid
targetVision.color("red")
delay(300)
targetVision.color("green")
targetVision.color("darkgreen")
delay(10)
}
}

View File

@ -16,8 +16,6 @@ import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.Object3D
import info.laht.threekt.geometries.BoxBufferGeometry
import info.laht.threekt.objects.Mesh
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlin.math.max
internal fun SolidGroup.varBox(
@ -62,7 +60,7 @@ internal class VariableBox(xSize: Number, ySize: Number, zSize: Number) : ThreeV
mesh.scale.set(xSize, ySize, zSize)
//add listener to object properties
propertyNameFlow.onEach { name ->
onPropertyChange(three.context) { name ->
when {
name.startsWith(GEOMETRY_KEY) -> {
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)
}
}.launchIn(three.context)
}
return mesh
}

View File

@ -13,11 +13,12 @@ public fun RBuilder.visionPropertyEditor(
descriptor: NodeDescriptor? = vision.descriptor,
key: Any? = null,
) {
card("Properties") {
propertyEditor(
provider = vision.ownProperties,
defaultProvider = vision.allProperties(),
updateFlow = vision.propertyNameFlow,
updateFlow = vision.propertyChanges,
descriptor = descriptor,
key = key)
}

View File

@ -7,6 +7,7 @@ import hep.dataforge.names.NameToken
import hep.dataforge.names.lastOrNull
import hep.dataforge.names.plus
import hep.dataforge.values.Value
import hep.dataforge.vision.hidden
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.channels.awaitClose
@ -66,6 +67,9 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
val itemName by useState { props.name ?: Name.EMPTY }
var item: MetaItem<*>? by useState { props.provider.getItem(itemName) }
val descriptorItem: ItemDescriptor? = props.descriptor?.get(itemName)
if(descriptorItem?.hidden == true) return //fail fast for hidden property
var actualItem: MetaItem<Meta>? by useState {
item ?: props.defaultProvider?.getItem(itemName) ?: descriptorItem?.defaultItem()
}
@ -106,8 +110,6 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
update()
}
if (actualItem is MetaItem.NodeItem) {
styledDiv {
css {

View File

@ -5,6 +5,7 @@ import hep.dataforge.names.Name
import hep.dataforge.names.NameToken
import hep.dataforge.names.asName
import hep.dataforge.names.plus
import kotlinx.coroutines.launch
/**
* A container for styles
@ -54,7 +55,9 @@ internal fun Vision.styleChanged(key: String, oldStyle: Meta?, newStyle: Meta?)
val tokens: Collection<Name> =
((oldStyle?.items?.keys ?: emptySet()) + (newStyle?.items?.keys ?: emptySet()))
.map { it.asName() }
tokens.forEach { parent?.notifyPropertyChanged(it) }
parent?.scope?.launch {
tokens.forEach { parent?.notifyPropertyChanged(it) }
}
}
if (this is VisionGroup) {
for (obj in this) {

View File

@ -9,9 +9,10 @@ import hep.dataforge.names.asName
import hep.dataforge.names.toName
import hep.dataforge.provider.Type
import hep.dataforge.vision.Vision.Companion.TYPE
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.serialization.Transient
/**
@ -29,7 +30,7 @@ public interface Vision : Described {
/**
* 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).
@ -55,17 +56,26 @@ public interface Vision : Described {
*/
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
* 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
*/
public fun notifyPropertyChanged(propertyName: Name): Unit
public suspend fun notifyPropertyChanged(propertyName: Name): Unit
/**
* 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
*/

View File

@ -11,8 +11,11 @@ import hep.dataforge.names.Name
import hep.dataforge.names.asName
import hep.dataforge.values.ValueType
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.SharedFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@ -44,7 +47,9 @@ public open class VisionBase : Vision {
properties = newProperties
newProperties.onChange(this) { name, oldItem, newItem ->
if (oldItem != newItem) {
notifyPropertyChanged(name)
scope.launch {
notifyPropertyChanged(name)
}
}
}
}
@ -77,14 +82,16 @@ public open class VisionBase : Vision {
@Synchronized
override fun setProperty(name: Name, item: MetaItem<*>?, notify: Boolean) {
getOrCreateConfig().setItem(name, item)
if(notify) {
notifyPropertyChanged(name)
if (notify) {
scope.launch {
notifyPropertyChanged(name)
}
}
}
override val descriptor: NodeDescriptor? get() = null
private fun updateStyles(names: List<String>) {
private suspend fun updateStyles(names: List<String>) {
names.mapNotNull { getStyle(it) }.asSequence()
.flatMap { it.items.asSequence() }
.distinctBy { it.key }
@ -94,17 +101,19 @@ public open class VisionBase : Vision {
}
@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) {
updateStyles(styles)
}
scope.launch {
_propertyInvalidationFlow.emit(propertyName)
}
propertyInvalidationFlow.emit(propertyName)
}
public fun configure(block: MutableMeta<*>.() -> Unit) {

View File

@ -67,6 +67,9 @@ public class VisionChange(
@Serializable(MetaSerializer::class) public val properties: Meta? = null,
public val children: Map<Name, VisionChange>? = null,
) {
init {
(vision as? VisionGroup)?.attachChildren()
}
}
@ -81,10 +84,10 @@ private fun CoroutineScope.collectChange(
) {
//Collect properties change
source.propertyNameFlow.onEach { propertyName ->
source.onPropertyChange(this) { propertyName ->
val newItem = source.getProperty(propertyName, inherit = false, includeStyles = false, includeDefaults = false)
collector().propertyChanged(name, propertyName, newItem)
}.launchIn(this)
}
if (source is VisionGroup) {
//Subscribe for children changes

View File

@ -28,7 +28,7 @@ public open class VisionGroupBase : VisionBase(), MutableVisionGroup {
*/
override val children: Map<NameToken, Vision> get() = childrenInternal
override fun notifyPropertyChanged(propertyName: Name) {
override suspend fun notifyPropertyChanged(propertyName: Name) {
super.notifyPropertyChanged(propertyName)
for (obj in this) {
obj.notifyPropertyChanged(propertyName)

View File

@ -1,15 +1,11 @@
package hep.dataforge.vision
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
@DslMarker
public annotation class VisionBuilder
public fun Sequence<MetaItem<*>?>.merge(): MetaItem<*>? {
return when (val first = firstOrNull { it != 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
public val Vision.properties: Config?
get() = (this as? VisionBase)?.properties

View File

@ -1,27 +0,0 @@
package hep.dataforge.vision
import hep.dataforge.meta.*
import hep.dataforge.meta.descriptors.ValueDescriptor
import hep.dataforge.meta.descriptors.attributes
/**
* Extension property to access the "widget" key of [ValueDescriptor]
*/
public var ValueDescriptor.widget: Meta
get() = attributes["widget"].node ?: Meta.EMPTY
set(value) {
attributes {
set("widget", value)
}
}
/**
* Extension property to access the "widget.type" key of [ValueDescriptor]
*/
public var ValueDescriptor.widgetType: String?
get() = attributes["widget.type"].string
set(value) {
attributes{
set("widget.type", value)
}
}

View File

@ -1,11 +1,13 @@
package hep.dataforge.vision
import hep.dataforge.meta.Meta
import hep.dataforge.meta.boolean
import hep.dataforge.meta.*
import hep.dataforge.meta.descriptors.ItemDescriptor
import hep.dataforge.meta.descriptors.NodeDescriptor
import hep.dataforge.meta.descriptors.ValueDescriptor
import hep.dataforge.meta.descriptors.attributes
import hep.dataforge.meta.get
import hep.dataforge.meta.set
import hep.dataforge.names.Name
import hep.dataforge.values.ValueType
import hep.dataforge.values.asValue
private const val INHERITED_DESCRIPTOR_ATTRIBUTE = "inherited"
private const val STYLE_DESCRIPTOR_ATTRIBUTE = "useStyles"
@ -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()
}

View File

@ -18,9 +18,6 @@ import org.w3c.dom.WebSocket
import org.w3c.dom.asList
import org.w3c.dom.get
import org.w3c.dom.url.URL
import kotlin.collections.HashMap
import kotlin.collections.forEach
import kotlin.collections.maxByOrNull
import kotlin.collections.set
import kotlin.reflect.KClass
@ -84,14 +81,18 @@ public class VisionClient : AbstractPlugin() {
renderVision(element, embeddedVision, outputMeta)
}
val endpoint = resolveEndpoint(element)
logger.info { "Vision server is resolved to $endpoint" }
element.attributes[OUTPUT_FETCH_ATTRIBUTE]?.let { attr ->
element.attributes[OUTPUT_FETCH_ATTRIBUTE]?.let {
val fetchUrl = URL(endpoint).apply {
val fetchUrl = if (attr.value.isBlank() || attr.value == "auto") {
val endpoint = resolveEndpoint(element)
logger.info { "Vision server is resolved to $endpoint" }
URL(endpoint).apply {
pathname += "/vision"
}
} else {
URL(attr.value)
}.apply {
searchParams.append("name", name)
pathname += "/vision"
}
logger.info { "Fetching vision data from $fetchUrl" }
@ -102,16 +103,22 @@ public class VisionClient : AbstractPlugin() {
renderVision(element, vision, outputMeta)
}
} 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 {
val wsUrl = URL(endpoint).apply {
pathname += "/ws"
element.attributes[OUTPUT_CONNECT_ATTRIBUTE]?.let { attr ->
val wsUrl = if (attr.value.isBlank() || attr.value == "auto") {
val endpoint = resolveEndpoint(element)
logger.info { "Vision server is resolved to $endpoint" }
URL(endpoint).apply {
pathname += "/ws"
}
} else {
URL(attr.value)
}.apply {
protocol = "ws"
searchParams.append("name", name)
}
@ -122,15 +129,20 @@ public class VisionClient : AbstractPlugin() {
onmessage = { messageEvent ->
val stringData: String? = messageEvent.data as? String
if (stringData != null) {
val dif = visionManager.jsonFormat.decodeFromString(
val change = visionManager.jsonFormat.decodeFromString(
VisionChange.serializer(),
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")
} else {
console.error ("WebSocket message data is not a string")
console.error("WebSocket message data is not a string")
}
}
onopen = {

View File

@ -4,8 +4,6 @@ import hep.dataforge.names.*
import hep.dataforge.vision.Vision
import javafx.scene.Group
import javafx.scene.Node
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlin.reflect.KClass
class FXReferenceFactory(val plugin: FX3DPlugin) : FX3DFactory<SolidReferenceGroup> {
@ -15,7 +13,7 @@ class FXReferenceFactory(val plugin: FX3DPlugin) : FX3DFactory<SolidReferenceGro
val prototype = obj.prototype
val node = plugin.buildNode(prototype)
obj.propertyNameFlow.onEach { name->
obj.onPropertyChange(plugin.context) { name->
if (name.firstOrNull()?.body == SolidReferenceGroup.REFERENCE_CHILD_PROPERTY_PREFIX) {
val childName = name.firstOrNull()?.index?.toName() ?: error("Wrong syntax for reference child property: '$name'")
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")
child.updateProperty(referenceChild, propertyName)
}
}.launchIn(plugin.context)
}
return node
}
}

View File

@ -7,8 +7,6 @@ import hep.dataforge.names.toName
import hep.dataforge.vision.Vision
import javafx.application.Platform
import javafx.beans.binding.ObjectBinding
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import tornadofx.*
/**
@ -18,7 +16,7 @@ class VisualObjectFXBinding(val fx: FX3DPlugin, val obj: Vision) {
private val bindings = HashMap<Name, ObjectBinding<MetaItem<*>?>>()
init {
obj.propertyNameFlow.onEach { name ->
obj.onPropertyChange(fx.context) { name ->
bindings.filter { it.key.startsWith(name) }.forEach { entry ->
Platform.runLater {
entry.value.invalidate()
@ -30,7 +28,7 @@ class VisualObjectFXBinding(val fx: FX3DPlugin, val obj: Vision) {
// bindings[currentName]?.invalidate()
// currentName = currentName.cutLast()
// }
}.launchIn(fx.context)
}
}
operator fun get(key: Name): ObjectBinding<MetaItem<*>?> {

View File

@ -41,6 +41,13 @@ import java.awt.Desktop
import java.net.URI
import kotlin.time.milliseconds
public enum class VisionServerDataMode {
EMBED,
FETCH,
CONNECT
}
/**
* A ktor plugin container with given [routing]
*/
@ -52,6 +59,7 @@ public class VisionServer internal constructor(
override val config: Config = Config()
public var updateInterval: Long by config.long(300, key = UPDATE_INTERVAL_KEY)
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
@ -72,10 +80,23 @@ public class VisionServer internal constructor(
val consumer = object : VisionTagConsumer<Any?>(consumer) {
override fun DIV.renderVision(name: Name, vision: Vision, outputMeta: Meta) {
visionMap[name] = vision
// Toggle updates
attributes[OUTPUT_FETCH_ATTRIBUTE] = "true"
attributes[OUTPUT_CONNECT_ATTRIBUTE] = "true"
// Toggle update mode
when (dataMode) {
VisionServerDataMode.EMBED -> {
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")
val vision: Vision = visions[name.toName()] ?: error("Plot with id='$name' not registered")
try {
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 ->
val json = VisionManager.defaultJson.encodeToString(
val json = visionManager.jsonFormat.encodeToString(
VisionChange.serializer(),
update
)

View File

@ -61,6 +61,7 @@ public interface Solid : Vision {
public val descriptor: NodeDescriptor by lazy {
NodeDescriptor {
value(VISIBLE_KEY) {
inherited = false
type(ValueType.BOOLEAN)
default(true)
}
@ -69,11 +70,14 @@ public interface Solid : Vision {
value(Vision.STYLE_KEY) {
type(ValueType.STRING)
multiple = true
hide()
}
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
set(value) {
position().x = value.toDouble()
notifyPropertyChanged(Solid.X_POSITION_KEY)
asyncNotifyPropertyChange(Solid.X_POSITION_KEY)
}
public var Solid.y: Number
get() = position?.y ?: 0f
set(value) {
position().y = value.toDouble()
notifyPropertyChanged(Solid.Y_POSITION_KEY)
asyncNotifyPropertyChange(Solid.Y_POSITION_KEY)
}
public var Solid.z: Number
get() = position?.z ?: 0f
set(value) {
position().z = value.toDouble()
notifyPropertyChanged(Solid.Z_POSITION_KEY)
asyncNotifyPropertyChange(Solid.Z_POSITION_KEY)
}
private fun Solid.rotation(): Point3D =
@ -176,21 +180,21 @@ public var Solid.rotationX: Number
get() = rotation?.x ?: 0f
set(value) {
rotation().x = value.toDouble()
notifyPropertyChanged(Solid.X_ROTATION_KEY)
asyncNotifyPropertyChange(Solid.X_ROTATION_KEY)
}
public var Solid.rotationY: Number
get() = rotation?.y ?: 0f
set(value) {
rotation().y = value.toDouble()
notifyPropertyChanged(Solid.Y_ROTATION_KEY)
asyncNotifyPropertyChange(Solid.Y_ROTATION_KEY)
}
public var Solid.rotationZ: Number
get() = rotation?.z ?: 0f
set(value) {
rotation().z = value.toDouble()
notifyPropertyChanged(Solid.Z_ROTATION_KEY)
asyncNotifyPropertyChange(Solid.Z_ROTATION_KEY)
}
private fun Solid.scale(): Point3D =
@ -200,19 +204,19 @@ public var Solid.scaleX: Number
get() = scale?.x ?: 1f
set(value) {
scale().x = value.toDouble()
notifyPropertyChanged(Solid.X_SCALE_KEY)
asyncNotifyPropertyChange(Solid.X_SCALE_KEY)
}
public var Solid.scaleY: Number
get() = scale?.y ?: 1f
set(value) {
scale().y = value.toDouble()
notifyPropertyChanged(Solid.Y_SCALE_KEY)
asyncNotifyPropertyChange(Solid.Y_SCALE_KEY)
}
public var Solid.scaleZ: Number
get() = scale?.z ?: 1f
set(value) {
scale().z = value.toDouble()
notifyPropertyChanged(Solid.Z_SCALE_KEY)
asyncNotifyPropertyChange(Solid.Z_SCALE_KEY)
}

View File

@ -42,7 +42,7 @@ public class SolidGroup : VisionGroupBase(), Solid, PrototypeHolder {
* Ger a prototype redirecting the request to the parent if prototype is not found
*/
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
@ -108,7 +108,7 @@ public fun VisionContainerBuilder<Vision>.group(name: String, action: SolidGroup
@Serializable(Prototypes.Companion::class)
internal class Prototypes(
children: Map<NameToken, Vision> = emptyMap(),
) : VisionGroupBase() {
) : VisionGroupBase(), PrototypeHolder {
init {
childrenInternal.putAll(children)
@ -155,4 +155,10 @@ internal class Prototypes(
mapSerializer.serialize(encoder, value.children)
}
}
override fun prototypes(builder: VisionContainerBuilder<Solid>.() -> Unit) {
apply(builder)
}
override fun getPrototype(name: Name): Solid? = get(name) as? Solid
}

View File

@ -89,18 +89,24 @@ public class SolidMaterial : Scheme() {
public override val descriptor: NodeDescriptor by lazy {
//must be lazy to avoid initialization bug
NodeDescriptor {
inherited = true
usesStyles = true
value(COLOR_KEY) {
inherited = true
usesStyles = true
type(ValueType.STRING, ValueType.NUMBER)
widgetType = "color"
}
// value(SPECULAR_COLOR_KEY) {
// inherited = true
// usesStyles = true
// type(ValueType.STRING, ValueType.NUMBER)
// widgetType = "color"
// }
value(SPECULAR_COLOR_KEY) {
inherited = true
usesStyles = true
type(ValueType.STRING, ValueType.NUMBER)
widgetType = "color"
hide()
}
value(OPACITY_KEY) {
inherited = true
usesStyles = true

View File

@ -5,9 +5,7 @@ import hep.dataforge.meta.descriptors.NodeDescriptor
import hep.dataforge.meta.descriptors.get
import hep.dataforge.names.*
import hep.dataforge.vision.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.CoroutineScope
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@ -128,14 +126,15 @@ public class SolidReferenceGroup(
error("Setting a parent for a reference child is not possible")
}
override val propertyNameFlow: Flow<Name>
get() = this@SolidReferenceGroup.propertyNameFlow.filter { name ->
name.startsWith(childToken(childName))
}.map { name ->
name.cutFirst()
override fun onPropertyChange(scope: CoroutineScope, callback: suspend (Name) -> Unit) {
this@SolidReferenceGroup.onPropertyChange(scope) { name ->
if (name.startsWith(childToken(childName))) {
callback(name.cutFirst())
}
}
}
override fun notifyPropertyChanged(propertyName: Name) {
override suspend fun notifyPropertyChanged(propertyName: Name) {
this@SolidReferenceGroup.notifyPropertyChanged(childPropertyName(childName, propertyName))
}

View File

@ -15,8 +15,6 @@ import info.laht.threekt.geometries.EdgesGeometry
import info.laht.threekt.geometries.WireframeGeometry
import info.laht.threekt.objects.LineSegments
import info.laht.threekt.objects.Mesh
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlin.reflect.KClass
/**
@ -45,7 +43,7 @@ public abstract class MeshThreeFactory<in T : Solid>(
}.applyProperties(obj)
//add listener to object properties
obj.propertyNameFlow.onEach { name ->
obj.onPropertyChange(three.updateScope) { name ->
when {
name.startsWith(Solid.GEOMETRY_KEY) -> {
val oldGeometry = mesh.geometry as BufferGeometry
@ -59,7 +57,7 @@ public abstract class MeshThreeFactory<in T : Solid>(
name.startsWith(EDGES_KEY) -> mesh.applyEdges(obj)
else -> mesh.updateProperty(obj, name)
}
}.launchIn(three.updateScope)
}
return mesh
}

View File

@ -47,7 +47,7 @@ public fun Object3D.updatePosition(obj: Vision) {
*/
public fun Object3D.updateProperty(source: Vision, propertyName: Name) {
if (this is Mesh && propertyName.startsWith(MATERIAL_KEY)) {
this.material = getMaterial(source, false)
this.material = getMaterial(source, true)
} else if (
propertyName.startsWith(Solid.POSITION_KEY)
|| propertyName.startsWith(Solid.ROTATION)

View File

@ -8,8 +8,6 @@ import info.laht.threekt.core.Object3D
import info.laht.threekt.geometries.TextBufferGeometry
import info.laht.threekt.objects.Mesh
import kotlinext.js.jsObject
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlin.reflect.KClass
/**
@ -27,10 +25,10 @@ public object ThreeLabelFactory : ThreeFactory<SolidLabel> {
})
return Mesh(textGeo, getMaterial(obj, true)).apply {
updatePosition(obj)
obj.propertyNameFlow.onEach { _ ->
obj.onPropertyChange(three.updateScope){ _ ->
//TODO
three.logger.warn{"Label parameter change not implemented"}
}.launchIn(three.updateScope)
}
}
}
}

View File

@ -9,8 +9,6 @@ import info.laht.threekt.core.Geometry
import info.laht.threekt.core.Object3D
import info.laht.threekt.math.Color
import info.laht.threekt.objects.LineSegments
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlin.reflect.KClass
public object ThreeLineFactory : ThreeFactory<PolyLine> {
@ -30,9 +28,9 @@ public object ThreeLineFactory : ThreeFactory<PolyLine> {
updatePosition(obj)
//layers.enable(obj.layer)
//add listener to object properties
obj.propertyNameFlow.onEach { propertyName ->
obj.onPropertyChange(three.updateScope) { propertyName ->
updateProperty(obj, propertyName)
}.launchIn(three.updateScope)
}
}
}

View File

@ -59,6 +59,10 @@ public object ThreeMaterials {
MeshPhongMaterial().apply {
color = meta[SolidMaterial.COLOR_KEY]?.getColor() ?: DEFAULT_COLOR
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
transparent = opacity < 1.0
wireframe = meta[SolidMaterial.WIREFRAME_KEY].boolean ?: false

View File

@ -67,7 +67,7 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
updatePosition(obj)
//obj.onChildrenChange()
obj.propertyNameFlow.onEach { name ->
obj.onPropertyChange(updateScope) { name ->
if (
name.startsWith(Solid.POSITION_KEY) ||
name.startsWith(Solid.ROTATION) ||
@ -78,7 +78,7 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
} else if (name == Vision.VISIBLE_KEY) {
visible = obj.visible ?: true
}
}.launchIn(updateScope)
}
obj.structureChanges.onEach { (nameToken, _, child) ->
// if (name.isEmpty()) {

View File

@ -9,8 +9,6 @@ import hep.dataforge.vision.solid.SolidReferenceGroup.Companion.REFERENCE_CHILD_
import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.Object3D
import info.laht.threekt.objects.Mesh
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlin.reflect.KClass
public object ThreeReferenceFactory : ThreeFactory<SolidReferenceGroup> {
@ -47,7 +45,7 @@ public object ThreeReferenceFactory : ThreeFactory<SolidReferenceGroup> {
//TODO apply child properties
obj.propertyNameFlow.onEach { name->
obj.onPropertyChange(three.updateScope) { name->
if (name.firstOrNull()?.body == REFERENCE_CHILD_PROPERTY_PREFIX) {
val childName = name.firstOrNull()?.index?.toName() ?: error("Wrong syntax for reference child property: '$name'")
val propertyName = name.cutFirst()
@ -57,7 +55,7 @@ public object ThreeReferenceFactory : ThreeFactory<SolidReferenceGroup> {
} else {
object3D.updateProperty(obj, name)
}
}.launchIn(three.updateScope)
}
return object3D