Change property and structure subscription to flows.

This commit is contained in:
Alexander Nozik 2020-12-15 19:18:04 +03:00
parent 85bb690699
commit b2ba92e745
34 changed files with 562 additions and 518 deletions

View File

@ -4,13 +4,14 @@
### Added ### Added
- Server module - Server module
- Change collector - Change collector
- Customizable accessors for colors
### Changed ### Changed
- Vision does not implement ItemProvider anymore. Property changes are done via `getProperty`/`setProperty` and `property` delegate. - Vision does not implement ItemProvider anymore. Property changes are done via `getProperty`/`setProperty` and `property` delegate.
- Point3D and Point2D are made separate classes instead of expect/actual (to split up different engines. - Point3D and Point2D are made separate classes instead of expect/actual (to split up different engines.
- JavaFX support moved to a separate module - JavaFX support moved to a separate module
- Threejs support moved to a separate module - Threejs support moved to a separate module
- \[Breaking change!\] Stylesheets are moved into properties under `@stylesheet` key - \[Format breaking change!\] Stylesheets are moved into properties under `@stylesheet` key
### Deprecated ### Deprecated

View File

@ -1,7 +1,6 @@
package ru.mipt.npm.muon.monitor package ru.mipt.npm.muon.monitor
import hep.dataforge.vision.removeAll import hep.dataforge.vision.removeAll
import hep.dataforge.vision.setProperty
import hep.dataforge.vision.solid.* import hep.dataforge.vision.solid.*
import ru.mipt.npm.muon.monitor.Monitor.CENTRAL_LAYER_Z import ru.mipt.npm.muon.monitor.Monitor.CENTRAL_LAYER_Z
import ru.mipt.npm.muon.monitor.Monitor.LOWER_LAYER_Z import ru.mipt.npm.muon.monitor.Monitor.LOWER_LAYER_Z
@ -63,7 +62,6 @@ class Model {
fun reset() { fun reset() {
map.values.forEach { map.values.forEach {
it.config
it.setProperty(SolidMaterial.MATERIAL_COLOR_KEY, null) it.setProperty(SolidMaterial.MATERIAL_COLOR_KEY, null)
} }
tracks.removeAll() tracks.removeAll()

View File

@ -11,7 +11,7 @@ import hep.dataforge.names.plus
*/ */
public inline class StyleSheet(private val owner: VisionGroup) { public inline class StyleSheet(private val owner: VisionGroup) {
private val styleNode get() = owner.properties?.get(STYLESHEET_KEY).node private val styleNode get() = owner.getOwnProperty(STYLESHEET_KEY).node
public val items: Map<NameToken, Meta>? get() = styleNode?.items?.mapValues { it.value.node ?: Meta.EMPTY } public val items: Map<NameToken, Meta>? get() = styleNode?.items?.mapValues { it.value.node ?: Meta.EMPTY }
@ -21,11 +21,7 @@ public inline class StyleSheet(private val owner: VisionGroup) {
* Define a style without notifying owner * Define a style without notifying owner
*/ */
public fun define(key: String, style: Meta?) { public fun define(key: String, style: Meta?) {
if (style == null) { owner.setProperty(STYLESHEET_KEY + key, style)
styleNode?.remove(key)
} else {
owner.config[STYLESHEET_KEY + key] = style
}
} }
/** /**
@ -58,7 +54,7 @@ internal fun Vision.styleChanged(key: String, oldStyle: Meta?, newStyle: Meta?)
val tokens: Collection<Name> = val tokens: Collection<Name> =
((oldStyle?.items?.keys ?: emptySet()) + (newStyle?.items?.keys ?: emptySet())) ((oldStyle?.items?.keys ?: emptySet()) + (newStyle?.items?.keys ?: emptySet()))
.map { it.asName() } .map { it.asName() }
tokens.forEach { parent?.propertyChanged(it) } tokens.forEach { parent?.notifyPropertyChanged(it) }
} }
if (this is VisionGroup) { if (this is VisionGroup) {
for (obj in this) { for (obj in this) {
@ -78,7 +74,7 @@ public val VisionGroup.styleSheet: StyleSheet get() = StyleSheet(this)
* Add style name to the list of styles to be resolved later. The style with given name does not necessary exist at the moment. * Add style name to the list of styles to be resolved later. The style with given name does not necessary exist at the moment.
*/ */
public fun Vision.useStyle(name: String) { public fun Vision.useStyle(name: String) {
styles = (properties[Vision.STYLE_KEY]?.stringList ?: emptyList()) + name styles = (getOwnProperty(Vision.STYLE_KEY)?.stringList ?: emptyList()) + name
} }
@ -86,7 +82,7 @@ public fun Vision.useStyle(name: String) {
* Find a style with given name for given [Vision]. The style is not necessary applied to this [Vision]. * Find a style with given name for given [Vision]. The style is not necessary applied to this [Vision].
*/ */
public tailrec fun Vision.getStyle(name: String): Meta? = public tailrec fun Vision.getStyle(name: String): Meta? =
properties?.get(StyleSheet.STYLESHEET_KEY + name).node ?: parent?.getStyle(name) getOwnProperty(StyleSheet.STYLESHEET_KEY + name).node ?: parent?.getStyle(name)
/** /**
* Resolve an item in all style layers * Resolve an item in all style layers

View File

@ -10,13 +10,14 @@ import hep.dataforge.provider.Type
import hep.dataforge.values.asValue import hep.dataforge.values.asValue
import hep.dataforge.vision.Vision.Companion.TYPE import hep.dataforge.vision.Vision.Companion.TYPE
import hep.dataforge.vision.Vision.Companion.VISIBLE_KEY import hep.dataforge.vision.Vision.Companion.VISIBLE_KEY
import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.Transient import kotlinx.serialization.Transient
/** /**
* A root type for display hierarchy * A root type for display hierarchy
*/ */
@Type(TYPE) @Type(TYPE)
public interface Vision : Configurable, Described { public interface Vision : Described {
/** /**
* The parent object of this one. If null, this one is a root. * The parent object of this one. If null, this one is a root.
@ -25,42 +26,52 @@ public interface Vision : Configurable, Described {
public var parent: VisionGroup? public var parent: VisionGroup?
/** /**
* Nullable version of [config] used to check if this [Vision] has custom properties * A fast accessor method to get own property (no inheritance or styles
*/ */
public val properties: Config? public fun getOwnProperty(name: Name): MetaItem<*>?
/** /**
* All properties including styles and prototypes if present, including inherited ones * Get property.
* @param inherit toggles parent node property lookup
* @param includeStyles toggles inclusion of
*/ */
public val allProperties: Laminate public fun getProperty(
name: Name,
inherit: Boolean = true,
includeStyles: Boolean = true,
includeDefaults: Boolean = true,
): MetaItem<*>?
/** /**
* Get property (including styles). [inherit] toggles parent node property lookup * Set the property value
*/ */
public fun getProperty(name: Name, inherit: Boolean = true): MetaItem<*>? public fun setProperty(name: Name, item: MetaItem<*>?)
/** /**
* Trigger property invalidation event. If [name] is empty, notify that the whole object is changed * 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 fun propertyChanged(name: Name): Unit public val propertyInvalidated: Flow<Name>
/** /**
* Add listener triggering on property change * Notify all listeners that a property has been changed and should be invalidated
*/ */
public fun onPropertyChange(owner: Any?, action: (Name) -> Unit): Unit public fun notifyPropertyChanged(propertyName: Name): Unit
/**
* Remove change listeners with given owner.
*/
public fun removeChangeListener(owner: Any?)
/** /**
* List of names of styles applied to this object. Order matters. Not inherited. * List of names of styles applied to this object. Order matters. Not inherited.
*/ */
public var styles: List<String> public var styles: List<String>
get() = properties[STYLE_KEY]?.stringList ?: emptyList() get() = getProperty(
STYLE_KEY,
inherit = false,
includeStyles = false,
includeDefaults = true
)?.stringList ?: emptyList()
set(value) { set(value) {
config[STYLE_KEY] = value setProperty(STYLE_KEY, value)
} }
/** /**
@ -78,24 +89,42 @@ public interface Vision : Configurable, Described {
} }
} }
/**
* Convenient accessor for all properties of a vision. Provided properties include styles and defaults, but do not inherit.
*/
public val Vision.properties: MutableItemProvider
get() = object : MutableItemProvider {
override fun getItem(name: Name): MetaItem<*>? = getProperty(name,
inherit = false,
includeStyles = true,
includeDefaults = true
)
override fun setItem(name: Name, item: MetaItem<*>?): Unit = setProperty(name, item)
}
/** /**
* Get [Vision] property using key as a String * Get [Vision] property using key as a String
*/ */
public fun Vision.getProperty(key: String, inherit: Boolean = true): MetaItem<*>? = public fun Vision.getProperty(
getProperty(key.toName(), inherit) key: String,
inherit: Boolean = true,
includeStyles: Boolean = true,
includeDefaults: Boolean = true,
): MetaItem<*>? = getProperty(key.toName(), inherit, includeStyles, includeDefaults)
/** /**
* A convenience method to pair [getProperty] * A convenience method to pair [getProperty]
*/ */
public fun Vision.setProperty(key: Name, value: Any?) { public fun Vision.setProperty(key: Name, value: Any?) {
config[key] = value properties[key] = value
} }
/** /**
* A convenience method to pair [getProperty] * A convenience method to pair [getProperty]
*/ */
public fun Vision.setProperty(key: String, value: Any?) { public fun Vision.setProperty(key: String, value: Any?) {
config[key] = value properties[key] = value
} }
/** /**
@ -103,27 +132,7 @@ public fun Vision.setProperty(key: String, value: Any?) {
*/ */
public var Vision.visible: Boolean? public var Vision.visible: Boolean?
get() = getProperty(VISIBLE_KEY).boolean get() = getProperty(VISIBLE_KEY).boolean
set(value) = config.setValue(VISIBLE_KEY, value?.asValue()) set(value) = setProperty(VISIBLE_KEY, value?.asValue())
///**
// * Convenience delegate for properties
// */
//public fun Vision.property(
// default: MetaItem<*>? = null,
// key: Name? = null,
// inherit: Boolean = true,
//): MutableItemDelegate =
// object : ReadWriteProperty<Any?, MetaItem<*>?> {
// override fun getValue(thisRef: Any?, property: KProperty<*>): MetaItem<*>? {
// val name = key ?: property.name.toName()
// return getProperty(name, inherit) ?: default
// }
//
// override fun setValue(thisRef: Any?, property: KProperty<*>, value: MetaItem<*>?) {
// val name = key ?: property.name.toName()
// setProperty(name, value)
// }
// }
public fun Vision.props(inherit: Boolean = true): MutableItemProvider = object : MutableItemProvider { public fun Vision.props(inherit: Boolean = true): MutableItemProvider = object : MutableItemProvider {
override fun getItem(name: Name): MetaItem<*>? { override fun getItem(name: Name): MetaItem<*>? {

View File

@ -3,15 +3,18 @@ package hep.dataforge.vision
import hep.dataforge.meta.* import hep.dataforge.meta.*
import hep.dataforge.meta.descriptors.NodeDescriptor import hep.dataforge.meta.descriptors.NodeDescriptor
import hep.dataforge.meta.descriptors.defaultItem import hep.dataforge.meta.descriptors.defaultItem
import hep.dataforge.meta.descriptors.defaultMeta
import hep.dataforge.meta.descriptors.get import hep.dataforge.meta.descriptors.get
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.names.asName import hep.dataforge.names.asName
import hep.dataforge.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.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient import kotlinx.serialization.Transient
import kotlin.jvm.Synchronized
internal data class PropertyListener( internal data class PropertyListener(
val owner: Any? = null, val owner: Any? = null,
@ -28,83 +31,89 @@ public open class VisionBase : Vision {
/** /**
* Object own properties excluding styles and inheritance * Object own properties excluding styles and inheritance
*/ */
override var properties: Config? = null @SerialName("properties")
protected set private var _properties: Config? = null
override val descriptor: NodeDescriptor? get() = null /**
* All own properties as a read-only Meta
*/
public val ownProperties: Meta get() = _properties?: Meta.EMPTY
protected fun updateStyles(names: List<String>) { @Synchronized
names.mapNotNull { getStyle(it) }.asSequence() private fun getOrCreateConfig(): Config {
.flatMap { it.items.asSequence() } if (_properties == null) {
.distinctBy { it.key } val newProperties = Config()
.forEach { _properties = newProperties
propertyChanged(it.key.asName()) newProperties.onChange(this) { name, oldItem, newItem ->
if (oldItem != newItem) {
notifyPropertyChanged(name)
} }
} }
}
return _properties!!
}
/** /**
* The config is initialized and assigned on-demand. * A fast accessor method to get own property (no inheritance or styles
* To avoid unnecessary allocations, one should access [getAllProperties] via [getProperty] instead.
*/ */
override val config: Config by lazy { override fun getOwnProperty(name: Name): MetaItem<*>? {
properties ?: Config().also { config -> return _properties?.getItem(name)
properties = config.also {
it.onChange(this) { name, _, _ -> propertyChanged(name) }
}
}
} }
@Transient override fun getProperty(
private val listeners = HashSet<PropertyListener>() name: Name,
inherit: Boolean,
override fun propertyChanged(name: Name) { includeStyles: Boolean,
if (name == STYLE_KEY) { includeDefaults: Boolean,
updateStyles(properties?.get(STYLE_KEY)?.stringList ?: emptyList()) ): MetaItem<*>? = sequence {
} yield(getOwnProperty(name))
for (listener in listeners) { if (includeStyles) {
listener.action(name)
}
}
override fun onPropertyChange(owner: Any?, action: (Name) -> Unit) {
listeners.add(PropertyListener(owner, action))
}
override fun removeChangeListener(owner: Any?) {
listeners.removeAll { owner == null || it.owner == owner }
}
/**
* All available properties in a layered form
*/
override val allProperties: Laminate
get() = Laminate(
properties,
allStyles,
parent?.allProperties,
descriptor?.defaultMeta(),
)
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? = sequence {
yield(properties?.get(name))
yieldAll(getStyleItems(name)) yieldAll(getStyleItems(name))
}
if (inherit) { if (inherit) {
yield(parent?.getProperty(name, inherit)) yield(parent?.getProperty(name, inherit))
} }
yield(descriptor?.get(name)?.defaultItem()) yield(descriptor?.get(name)?.defaultItem())
}.merge() }.merge()
/** @Synchronized
* Reset all properties to their default values override fun setProperty(name: Name, item: MetaItem<*>?) {
*/ getOrCreateConfig().setItem(name, item)
public fun resetProperties() { notifyPropertyChanged(name)
properties?.removeListener(this) }
properties = null
override val descriptor: NodeDescriptor? get() = null
private fun updateStyles(names: List<String>) {
names.mapNotNull { getStyle(it) }.asSequence()
.flatMap { it.items.asSequence() }
.distinctBy { it.key }
.forEach {
notifyPropertyChanged(it.key.asName())
}
}
private val _propertyInvalidationFlow: MutableSharedFlow<Name> = MutableSharedFlow(
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
override val propertyInvalidated: SharedFlow<Name> get() = _propertyInvalidationFlow
override fun notifyPropertyChanged(propertyName: Name) {
if (propertyName == STYLE_KEY) {
updateStyles(properties.getItem(STYLE_KEY)?.stringList ?: emptyList())
}
_propertyInvalidationFlow.tryEmit(propertyName)
}
public fun configure(block: MutableMeta<*>.() -> Unit) {
getOrCreateConfig().block()
} }
override fun update(change: VisionChange) { override fun update(change: VisionChange) {
change.propertyChange[Name.EMPTY]?.let { change.properties?.let {
config.update(it) getOrCreateConfig().update(it)
} }
} }

View File

@ -6,7 +6,10 @@ import hep.dataforge.names.plus
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.serialization.* import kotlinx.serialization.*
import kotlin.jvm.Synchronized
import kotlin.time.Duration import kotlin.time.Duration
/** /**
@ -14,29 +17,41 @@ import kotlin.time.Duration
*/ */
public class VisionChangeBuilder : VisionContainerBuilder<Vision> { public class VisionChangeBuilder : VisionContainerBuilder<Vision> {
private val propertyChange = HashMap<Name, Config>() private var reset: Boolean = false
private val childrenChange = HashMap<Name, Vision?>() private var vision: Vision? = null
private val propertyChange = Config()
private val children: HashMap<Name, VisionChangeBuilder> = HashMap()
public fun isEmpty(): Boolean = propertyChange.isEmpty() && childrenChange.isEmpty() public fun isEmpty(): Boolean = propertyChange.isEmpty() && propertyChange.isEmpty() && children.isEmpty()
@Synchronized
private fun getOrPutChild(visionName: Name): VisionChangeBuilder =
children.getOrPut(visionName) { VisionChangeBuilder() }
public fun propertyChanged(visionName: Name, propertyName: Name, item: MetaItem<*>?) { public fun propertyChanged(visionName: Name, propertyName: Name, item: MetaItem<*>?) {
propertyChange if (visionName == Name.EMPTY) {
.getOrPut(visionName) { Config() } propertyChange[propertyName] = item
.setItem(propertyName, item) } else {
getOrPutChild(visionName).propertyChanged(Name.EMPTY, propertyName, item)
}
} }
override fun set(name: Name, child: Vision?) { override fun set(name: Name, child: Vision?) {
childrenChange[name] = child getOrPutChild(name).apply {
vision = child
reset = vision == null
}
} }
/** /**
* Isolate collected changes by creating detached copies of given visions * Isolate collected changes by creating detached copies of given visions
*/ */
public fun isolate(manager: VisionManager): VisionChange = VisionChange( public fun isolate(manager: VisionManager): VisionChange = VisionChange(
propertyChange.mapValues { it.value.seal() }, reset,
childrenChange.mapValues { it.value?.isolate(manager) } vision?.isolate(manager),
if (propertyChange.isEmpty()) null else propertyChange.seal(),
if (children.isEmpty()) null else children.mapValues { it.value.isolate(manager) }
) )
//TODO optimize isolation for visions without parents?
} }
private fun Vision.isolate(manager: VisionManager): Vision { private fun Vision.isolate(manager: VisionManager): Vision {
@ -46,16 +61,13 @@ private fun Vision.isolate(manager: VisionManager): Vision {
} }
@Serializable @Serializable
public data class VisionChange( public class VisionChange(
val propertyChange: Map<Name, @Serializable(MetaSerializer::class) Meta>, public val reset: Boolean = false,
val childrenChange: Map<Name, Vision?>, public val vision: Vision? = null,
public val properties: Meta? = null,
public val children: Map<Name, VisionChange>? = null,
) { ) {
public fun isEmpty(): Boolean = propertyChange.isEmpty() && childrenChange.isEmpty()
/**
* A shortcut to the top level property dif
*/
public val properties: Meta? get() = propertyChange[Name.EMPTY]
} }
public inline fun VisionChange(manager: VisionManager, block: VisionChangeBuilder.() -> Unit): VisionChange = public inline fun VisionChange(manager: VisionManager, block: VisionChangeBuilder.() -> Unit): VisionChange =
@ -69,17 +81,10 @@ private fun CoroutineScope.collectChange(
) { ) {
//Collect properties change //Collect properties change
source.config.onChange(this) { propertyName, oldItem, newItem -> source.propertyInvalidated.onEach { propertyName ->
if (oldItem != newItem) { val newItem = source.getProperty(propertyName, inherit = false, includeStyles = false, includeDefaults = false)
launch {
collector().propertyChanged(name, propertyName, newItem) collector().propertyChanged(name, propertyName, newItem)
} }.launchIn(this)
}
}
coroutineContext[Job]?.invokeOnCompletion {
source.config.removeListener(this)
}
if (source is VisionGroup) { if (source is VisionGroup) {
//Subscribe for children changes //Subscribe for children changes
@ -89,17 +94,12 @@ private fun CoroutineScope.collectChange(
//Subscribe for structure change //Subscribe for structure change
if (source is MutableVisionGroup) { if (source is MutableVisionGroup) {
source.onStructureChange(this) { token, before, after -> source.structureChanges.onEach { (token, _, after) ->
before?.removeChangeListener(this)
(before as? MutableVisionGroup)?.removeStructureChangeListener(this)
if (after != null) { if (after != null) {
collectChange(name + token, after, collector) collectChange(name + token, after, collector)
} }
collector()[name + token] = after collector()[name + token] = after
} }.launchIn(this)
coroutineContext[Job]?.invokeOnCompletion {
source.removeStructureChangeListener(this)
}
} }
} }
} }
@ -111,7 +111,8 @@ public fun Vision.flowChanges(
): Flow<VisionChange> = flow { ): Flow<VisionChange> = flow {
var collector = VisionChangeBuilder() var collector = VisionChangeBuilder()
manager.context.collectChange(Name.EMPTY, this@flowChanges) { collector } coroutineScope {
collectChange(Name.EMPTY, this@flowChanges) { collector }
while (currentCoroutineContext().isActive) { while (currentCoroutineContext().isActive) {
//Wait for changes to accumulate //Wait for changes to accumulate
@ -125,3 +126,4 @@ public fun Vision.flowChanges(
} }
} }
} }
}

View File

@ -2,6 +2,7 @@ package hep.dataforge.vision
import hep.dataforge.names.* import hep.dataforge.names.*
import hep.dataforge.provider.Provider import hep.dataforge.provider.Provider
import kotlinx.coroutines.flow.Flow
public interface VisionContainer<out V : Vision> { public interface VisionContainer<out V : Vision> {
public operator fun get(name: Name): V? public operator fun get(name: Name): V?
@ -75,19 +76,12 @@ public interface VisionContainerBuilder<in V : Vision> {
*/ */
public interface MutableVisionGroup : VisionGroup, VisionContainerBuilder<Vision> { public interface MutableVisionGroup : VisionGroup, VisionContainerBuilder<Vision> {
/** public data class StructureChange(val token: NameToken, val before: Vision?, val after: Vision?)
* Add listener for children structure change.
* @param owner the handler to properly remove listeners
* @param action First argument of the action is the name of changed child. Second argument is the new value of the object.
*/
public fun onStructureChange(owner: Any?, action: (token: NameToken, before: Vision?, after: Vision?) -> Unit)
/** /**
* Remove children change listener * Flow structure changes of this group. Unconsumed changes are discarded
*/ */
public fun removeStructureChangeListener(owner: Any?) public val structureChanges: Flow<StructureChange>
// public operator fun set(name: Name, child: Vision?)
} }
public operator fun <V : Vision> VisionContainer<V>.get(str: String): V? = get(str.toName()) public operator fun <V : Vision> VisionContainer<V>.get(str: String): V? = get(str.toName())

View File

@ -1,7 +1,9 @@
package hep.dataforge.vision package hep.dataforge.vision
import hep.dataforge.meta.configure
import hep.dataforge.names.* import hep.dataforge.names.*
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient import kotlinx.serialization.Transient
@ -26,54 +28,25 @@ public open class VisionGroupBase : VisionBase(), MutableVisionGroup {
*/ */
override val children: Map<NameToken, Vision> get() = childrenInternal override val children: Map<NameToken, Vision> get() = childrenInternal
override fun propertyChanged(name: Name) { override fun notifyPropertyChanged(propertyName: Name) {
super.propertyChanged(name) super.notifyPropertyChanged(propertyName)
for (obj in this) { for (obj in this) {
obj.propertyChanged(name) obj.notifyPropertyChanged(propertyName)
} }
} }
private data class StructureChangeListener(
val owner: Any?,
val callback: (token: NameToken, before: Vision?, after: Vision?) -> Unit,
)
@Transient @Transient
private val structureChangeListeners = HashSet<StructureChangeListener>() private val _structureChanges: MutableSharedFlow<MutableVisionGroup.StructureChange> = MutableSharedFlow(
onBufferOverflow = BufferOverflow.DROP_OLDEST
/**
* Add listener for children change
*/
override fun onStructureChange(owner: Any?, action: (token: NameToken, before: Vision?, after: Vision?) -> Unit) {
structureChangeListeners.add(
StructureChangeListener(
owner,
action
) )
)
}
/** override val structureChanges: SharedFlow<MutableVisionGroup.StructureChange> get() = _structureChanges
* Remove children change listener
*/
override fun removeStructureChangeListener(owner: Any?) {
structureChangeListeners.removeAll { owner == null || it.owner === owner }
}
/** /**
* Propagate children change event upwards * Propagate children change event upwards
*/ */
protected fun childrenChanged(name: NameToken, before: Vision?, after: Vision?) { private fun childrenChanged(name: NameToken, before: Vision?, after: Vision?) {
structureChangeListeners.forEach { it.callback(name, before, after) } _structureChanges.tryEmit(MutableVisionGroup.StructureChange(name, before, after))
}
/**
* Remove a child with given name token
*/
public fun removeChild(token: NameToken): Vision? {
val removed = childrenInternal.remove(token)
removed?.parent = null
return removed
} }
/** /**
@ -91,14 +64,24 @@ public open class VisionGroupBase : VisionBase(), MutableVisionGroup {
/** /**
* Set parent for given child and attach it * Set parent for given child and attach it
*/ */
private fun attachChild(token: NameToken, child: Vision) { private fun attachChild(token: NameToken, child: Vision?) {
if (child.parent == null) { val before = children[token]
when {
child == null -> {
childrenInternal.remove(token)
}
child.parent == null -> {
child.parent = this child.parent = this
childrenInternal[token] = child childrenInternal[token] = child
} else if (child.parent !== this) { }
child.parent !== this -> {
error("Can't reassign existing parent for $child") error("Can't reassign existing parent for $child")
} }
} }
if (before != child) {
childrenChanged(token, before, child)
}
}
/** /**
* Recursively create a child group * Recursively create a child group
@ -133,14 +116,8 @@ public open class VisionGroupBase : VisionBase(), MutableVisionGroup {
} }
name.length == 1 -> { name.length == 1 -> {
val token = name.tokens.first() val token = name.tokens.first()
val before = children[token]
if (child == null) {
removeChild(token)
} else {
attachChild(token, child) attachChild(token, child)
} }
childrenChanged(token, before, child)
}
else -> { else -> {
//TODO add safety check //TODO add safety check
val parent = (get(name.cutLast()) as? MutableVisionGroup) ?: createGroups(name.cutLast()) val parent = (get(name.cutLast()) as? MutableVisionGroup) ?: createGroups(name.cutLast())
@ -150,18 +127,12 @@ public open class VisionGroupBase : VisionBase(), MutableVisionGroup {
} }
override fun update(change: VisionChange) { override fun update(change: VisionChange) {
//update stylesheet change.children?.forEach { (name, change) ->
// val changeStyleSheet = change.styleSheet when {
// if (changeStyleSheet != null) { change.reset -> set(name, null)
// styleSheet { change.vision != null -> set(name, change.vision)
// update(changeStyleSheet) else -> get(name)?.update(change)
// }
// }
change.propertyChange.forEach {(childName,configChange)->
get(childName)?.configure(configChange)
} }
change.childrenChange.forEach { (name, child) ->
set(name, child)
} }
super.update(change) super.update(change)
} }

View File

@ -1,9 +1,7 @@
package hep.dataforge.vision package hep.dataforge.vision
import hep.dataforge.meta.Laminate import hep.dataforge.meta.*
import hep.dataforge.meta.MetaItem
import hep.dataforge.meta.descriptors.NodeDescriptor import hep.dataforge.meta.descriptors.NodeDescriptor
import hep.dataforge.meta.node
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.values.ValueType import hep.dataforge.values.ValueType
import hep.dataforge.values.asValue import hep.dataforge.values.asValue
@ -31,3 +29,19 @@ public inline fun <reified E : Enum<E>> NodeDescriptor.enum(key: Name, default:
} }
allowedValues = enumValues<E>().map { it.asValue() } allowedValues = enumValues<E>().map { it.asValue() }
} }
@DFExperimental
public val Vision.ownProperties: Meta?
get() = (this as? VisionBase)?.ownProperties
@DFExperimental
public val Vision.describedProperties: Meta
get() = Meta {
descriptor?.items?.forEach { (key, _) ->
key put getProperty(key)
}
}
public fun Vision.configure(meta: Meta?): Unit = update(VisionChange(properties = meta))
public fun Vision.configure(block: MutableMeta<*>.() -> Unit): Unit = configure(Meta(block))

View File

@ -1,7 +1,6 @@
package hep.dataforge.vision.html package hep.dataforge.vision.html
import hep.dataforge.meta.DFExperimental import hep.dataforge.meta.DFExperimental
import hep.dataforge.meta.configure
import hep.dataforge.meta.set import hep.dataforge.meta.set
import hep.dataforge.vision.VisionBase import hep.dataforge.vision.VisionBase
import kotlinx.html.* import kotlinx.html.*
@ -35,7 +34,7 @@ class HtmlTagTest {
div { div {
h2 { +"Properties" } h2 { +"Properties" }
ul { ul {
vision.properties?.items?.forEach { (vision as? VisionBase)?.ownProperties?.items?.forEach {
li { li {
a { +it.key.toString() } a { +it.key.toString() }
p { +it.value.toString() } p { +it.value.toString() }

View File

@ -1,12 +1,8 @@
package hep.dataforge.vision.editor package hep.dataforge.vision.editor
import hep.dataforge.meta.Config import hep.dataforge.meta.*
import hep.dataforge.meta.Meta
import hep.dataforge.meta.descriptors.NodeDescriptor import hep.dataforge.meta.descriptors.NodeDescriptor
import hep.dataforge.meta.update import hep.dataforge.vision.*
import hep.dataforge.vision.Vision
import hep.dataforge.vision.getStyle
import hep.dataforge.vision.setProperty
import javafx.beans.binding.Binding import javafx.beans.binding.Binding
import javafx.beans.property.SimpleObjectProperty import javafx.beans.property.SimpleObjectProperty
import javafx.scene.Node import javafx.scene.Node
@ -23,8 +19,8 @@ class VisualObjectEditorFragment(val selector: (Vision) -> Meta) : Fragment() {
constructor( constructor(
item: Vision?, item: Vision?,
descriptor: NodeDescriptor?, descriptor: NodeDescriptor?,
selector: (Vision) -> Config = { it.config } selector: (Vision) -> MutableItemProvider = { it.properties }
) : this(selector) { ) : this({it.describedProperties}) {
this.item = item this.item = item
this.descriptorProperty.set(descriptor) this.descriptorProperty.set(descriptor)
} }

View File

@ -45,7 +45,7 @@ class FX3DPlugin : AbstractPlugin() {
fun buildNode(obj: Solid): Node { fun buildNode(obj: Solid): Node {
val binding = VisualObjectFXBinding(obj) val binding = VisualObjectFXBinding(obj)
return when (obj) { return when (obj) {
is SolidReference -> referenceFactory(obj, binding) is SolidReferenceGroup -> referenceFactory(obj, binding)
is SolidGroup -> { is SolidGroup -> {
Group(obj.children.mapNotNull { (token, obj) -> Group(obj.children.mapNotNull { (token, obj) ->
(obj as? Solid)?.let { (obj as? Solid)?.let {

View File

@ -6,15 +6,15 @@ import javafx.scene.Group
import javafx.scene.Node import javafx.scene.Node
import kotlin.reflect.KClass import kotlin.reflect.KClass
class FXReferenceFactory(val plugin: FX3DPlugin) : FX3DFactory<SolidReference> { class FXReferenceFactory(val plugin: FX3DPlugin) : FX3DFactory<SolidReferenceGroup> {
override val type: KClass<in SolidReference> get() = SolidReference::class override val type: KClass<in SolidReferenceGroup> get() = SolidReferenceGroup::class
override fun invoke(obj: SolidReference, binding: VisualObjectFXBinding): Node { override fun invoke(obj: SolidReferenceGroup, binding: VisualObjectFXBinding): Node {
val prototype = obj.prototype val prototype = obj.prototype
val node = plugin.buildNode(prototype) val node = plugin.buildNode(prototype)
obj.onPropertyChange(this) { name-> obj.onPropertyChange(this) { name->
if (name.firstOrNull()?.body == SolidReference.REFERENCE_CHILD_PROPERTY_PREFIX) { if (name.firstOrNull()?.body == SolidReferenceGroup.REFERENCE_CHILD_PROPERTY_PREFIX) {
val childName = name.firstOrNull()?.index?.toName() ?: error("Wrong syntax for reference child property: '$name'") val childName = name.firstOrNull()?.index?.toName() ?: error("Wrong syntax for reference child property: '$name'")
val propertyName = name.cutFirst() val propertyName = name.cutFirst()
val referenceChild = obj[childName] ?: error("Reference child with name '$childName' not found") val referenceChild = obj[childName] ?: error("Reference child with name '$childName' not found")

View File

@ -2,12 +2,12 @@ package hep.dataforge.vision.gdml
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.meta.MetaBuilder import hep.dataforge.meta.MetaBuilder
import hep.dataforge.meta.set
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.names.asName import hep.dataforge.names.asName
import hep.dataforge.names.plus import hep.dataforge.names.plus
import hep.dataforge.names.toName import hep.dataforge.names.toName
import hep.dataforge.vision.set import hep.dataforge.vision.set
import hep.dataforge.vision.setProperty
import hep.dataforge.vision.solid.* import hep.dataforge.vision.solid.*
import hep.dataforge.vision.solid.SolidMaterial.Companion.MATERIAL_COLOR_KEY import hep.dataforge.vision.solid.SolidMaterial.Companion.MATERIAL_COLOR_KEY
import hep.dataforge.vision.styleSheet import hep.dataforge.vision.styleSheet
@ -51,13 +51,13 @@ private class GDMLTransformer(val settings: GDMLTransformerSettings) {
private val proto = SolidGroup() private val proto = SolidGroup()
private val solids = proto.group(solidsName) { private val solids = proto.group(solidsName) {
config["edges.enabled"] = false setProperty("edges.enabled", false)
} }
private val referenceStore = HashMap<Name, MutableList<SolidReference>>() private val referenceStore = HashMap<Name, MutableList<SolidReferenceGroup>>()
private fun proxySolid(root: GDML, group: SolidGroup, solid: GDMLSolid, name: String): SolidReference { private fun proxySolid(root: GDML, group: SolidGroup, solid: GDMLSolid, name: String): SolidReferenceGroup {
val templateName = solidsName + name val templateName = solidsName + name
if (proto[templateName] == null) { if (proto[templateName] == null) {
solids.addSolid(root, solid, name) solids.addSolid(root, solid, name)
@ -67,7 +67,7 @@ private class GDMLTransformer(val settings: GDMLTransformerSettings) {
return ref return ref
} }
private fun proxyVolume(root: GDML, group: SolidGroup, physVolume: GDMLPhysVolume, volume: GDMLGroup): SolidReference { private fun proxyVolume(root: GDML, group: SolidGroup, physVolume: GDMLPhysVolume, volume: GDMLGroup): SolidReferenceGroup {
val templateName = volumesName + volume.name.asName() val templateName = volumesName + volume.name.asName()
if (proto[templateName] == null) { if (proto[templateName] == null) {
proto[templateName] = volume(root, volume) proto[templateName] = volume(root, volume)

View File

@ -2,16 +2,10 @@ package hep.dataforge.vision.gdml
import hep.dataforge.meta.DFExperimental import hep.dataforge.meta.DFExperimental
import hep.dataforge.meta.sequence import hep.dataforge.meta.sequence
import hep.dataforge.meta.set import hep.dataforge.vision.Vision
import hep.dataforge.names.Name import hep.dataforge.vision.ownProperties
import hep.dataforge.names.toName import hep.dataforge.vision.properties
import hep.dataforge.vision.*
import hep.dataforge.vision.solid.* import hep.dataforge.vision.solid.*
import hep.dataforge.vision.visitor.VisionVisitor
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
import mu.KotlinLogging
public expect class Counter() { public expect class Counter() {
public fun get(): Int public fun get(): Int
@ -24,6 +18,7 @@ private fun Point3D?.safePlus(other: Point3D?): Point3D? = if (this == null && o
(this ?: Point3D(0, 0, 0)) + (other ?: Point3D(0, 0, 0)) (this ?: Point3D(0, 0, 0)) + (other ?: Point3D(0, 0, 0))
} }
@DFExperimental
internal fun Vision.updateFrom(other: Vision): Vision { internal fun Vision.updateFrom(other: Vision): Vision {
if (this is Solid && other is Solid) { if (this is Solid && other is Solid) {
position = position.safePlus(other.position) position = position.safePlus(other.position)
@ -33,9 +28,9 @@ internal fun Vision.updateFrom(other: Vision): Vision {
scaleY = scaleY.toDouble() * other.scaleY.toDouble() scaleY = scaleY.toDouble() * other.scaleY.toDouble()
scaleZ = scaleZ.toDouble() * other.scaleZ.toDouble() scaleZ = scaleZ.toDouble() * other.scaleZ.toDouble()
} }
other.properties?.sequence()?.forEach { (name, item) -> other.ownProperties?.sequence()?.forEach { (name, item) ->
if (properties?.getItem(name) == null) { if (properties.getItem(name) == null) {
config[name] = item setProperty(name, item)
} }
} }
} }

View File

@ -1,4 +1,3 @@
package hep.dataforge.vision.solid package hep.dataforge.vision.solid
import hep.dataforge.meta.update import hep.dataforge.meta.update
@ -18,7 +17,7 @@ public enum class CompositeType {
public class Composite( public class Composite(
public val compositeType: CompositeType, public val compositeType: CompositeType,
public val first: Solid, public val first: Solid,
public val second: Solid public val second: Solid,
) : SolidBase(), Solid, VisionGroup { ) : SolidBase(), Solid, VisionGroup {
init { init {
@ -34,15 +33,13 @@ public class Composite(
public inline fun VisionContainerBuilder<Solid>.composite( public inline fun VisionContainerBuilder<Solid>.composite(
type: CompositeType, type: CompositeType,
name: String = "", name: String = "",
builder: SolidGroup.() -> Unit builder: SolidGroup.() -> Unit,
): Composite { ): Composite {
val group = SolidGroup().apply(builder) val group = SolidGroup().apply(builder)
val children = group.children.values.filterIsInstance<Solid>() val children = group.children.values.filterIsInstance<Solid>()
if (children.size != 2) error("Composite requires exactly two children") if (children.size != 2) error("Composite requires exactly two children")
return Composite(type, children[0], children[1]).also { return Composite(type, children[0], children[1]).also {
it.config.update(group.config) it.configure { update(group.ownProperties) }
//it.material = group.material
if (group.position != null) { if (group.position != null) {
it.position = group.position it.position = group.position
} }
@ -65,5 +62,8 @@ public inline fun VisionContainerBuilder<Solid>.subtract(name: String = "", buil
composite(CompositeType.SUBTRACT, name, builder = builder) composite(CompositeType.SUBTRACT, name, builder = builder)
@VisionBuilder @VisionBuilder
public inline fun VisionContainerBuilder<Solid>.intersect(name: String = "", builder: SolidGroup.() -> Unit): Composite = public inline fun VisionContainerBuilder<Solid>.intersect(
name: String = "",
builder: SolidGroup.() -> Unit,
): Composite =
composite(CompositeType.INTERSECT, name, builder = builder) composite(CompositeType.INTERSECT, name, builder = builder)

View File

@ -1,18 +1,17 @@
package hep.dataforge.vision.solid package hep.dataforge.vision.solid
import hep.dataforge.meta.* import hep.dataforge.meta.boolean
import hep.dataforge.meta.descriptors.NodeDescriptor import hep.dataforge.meta.descriptors.NodeDescriptor
import hep.dataforge.meta.enum
import hep.dataforge.meta.int
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.names.asName import hep.dataforge.names.asName
import hep.dataforge.names.plus import hep.dataforge.names.plus
import hep.dataforge.values.ValueType import hep.dataforge.values.ValueType
import hep.dataforge.values.asValue import hep.dataforge.values.asValue
import hep.dataforge.vision.Vision import hep.dataforge.vision.*
import hep.dataforge.vision.Vision.Companion.VISIBLE_KEY import hep.dataforge.vision.Vision.Companion.VISIBLE_KEY
import hep.dataforge.vision.VisionBuilder
import hep.dataforge.vision.enum
import hep.dataforge.vision.layout.Output import hep.dataforge.vision.layout.Output
import hep.dataforge.vision.setProperty
import hep.dataforge.vision.solid.Solid.Companion.DETAIL_KEY import hep.dataforge.vision.solid.Solid.Companion.DETAIL_KEY
import hep.dataforge.vision.solid.Solid.Companion.IGNORE_KEY import hep.dataforge.vision.solid.Solid.Companion.IGNORE_KEY
import hep.dataforge.vision.solid.Solid.Companion.LAYER_KEY import hep.dataforge.vision.solid.Solid.Companion.LAYER_KEY
@ -90,7 +89,7 @@ public interface Solid : Vision {
var result = +(solid.position?.hashCode() ?: 0) var result = +(solid.position?.hashCode() ?: 0)
result = 31 * result + (solid.rotation?.hashCode() ?: 0) result = 31 * result + (solid.rotation?.hashCode() ?: 0)
result = 31 * result + (solid.scale?.hashCode() ?: 0) result = 31 * result + (solid.scale?.hashCode() ?: 0)
result = 31 * result + (solid.properties?.hashCode() ?: 0) result = 31 * result + solid.properties.hashCode()
return result return result
} }
} }
@ -100,9 +99,9 @@ public interface Solid : Vision {
* Get the layer number this solid belongs to. Return 0 if layer is not defined. * Get the layer number this solid belongs to. Return 0 if layer is not defined.
*/ */
public var Solid.layer: Int public var Solid.layer: Int
get() = properties?.getItem(LAYER_KEY).int ?: 0 get() = properties.getItem(LAYER_KEY).int ?: 0
set(value) { set(value) {
config[LAYER_KEY] = value.asValue() setProperty(LAYER_KEY, value)
} }
@VisionBuilder @VisionBuilder
@ -153,21 +152,21 @@ public var Solid.x: Number
get() = position?.x ?: 0f get() = position?.x ?: 0f
set(value) { set(value) {
position().x = value.toDouble() position().x = value.toDouble()
propertyChanged(Solid.X_POSITION_KEY) notifyPropertyChanged(Solid.X_POSITION_KEY)
} }
public var Solid.y: Number public var Solid.y: Number
get() = position?.y ?: 0f get() = position?.y ?: 0f
set(value) { set(value) {
position().y = value.toDouble() position().y = value.toDouble()
propertyChanged(Solid.Y_POSITION_KEY) notifyPropertyChanged(Solid.Y_POSITION_KEY)
} }
public var Solid.z: Number public var Solid.z: Number
get() = position?.z ?: 0f get() = position?.z ?: 0f
set(value) { set(value) {
position().z = value.toDouble() position().z = value.toDouble()
propertyChanged(Solid.Z_POSITION_KEY) notifyPropertyChanged(Solid.Z_POSITION_KEY)
} }
private fun Solid.rotation(): Point3D = private fun Solid.rotation(): Point3D =
@ -177,21 +176,21 @@ public var Solid.rotationX: Number
get() = rotation?.x ?: 0f get() = rotation?.x ?: 0f
set(value) { set(value) {
rotation().x = value.toDouble() rotation().x = value.toDouble()
propertyChanged(Solid.X_ROTATION_KEY) notifyPropertyChanged(Solid.X_ROTATION_KEY)
} }
public var Solid.rotationY: Number public var Solid.rotationY: Number
get() = rotation?.y ?: 0f get() = rotation?.y ?: 0f
set(value) { set(value) {
rotation().y = value.toDouble() rotation().y = value.toDouble()
propertyChanged(Solid.Y_ROTATION_KEY) notifyPropertyChanged(Solid.Y_ROTATION_KEY)
} }
public var Solid.rotationZ: Number public var Solid.rotationZ: Number
get() = rotation?.z ?: 0f get() = rotation?.z ?: 0f
set(value) { set(value) {
rotation().z = value.toDouble() rotation().z = value.toDouble()
propertyChanged(Solid.Z_ROTATION_KEY) notifyPropertyChanged(Solid.Z_ROTATION_KEY)
} }
private fun Solid.scale(): Point3D = private fun Solid.scale(): Point3D =
@ -201,19 +200,19 @@ public var Solid.scaleX: Number
get() = scale?.x ?: 1f get() = scale?.x ?: 1f
set(value) { set(value) {
scale().x = value.toDouble() scale().x = value.toDouble()
propertyChanged(Solid.X_SCALE_KEY) notifyPropertyChanged(Solid.X_SCALE_KEY)
} }
public var Solid.scaleY: Number public var Solid.scaleY: Number
get() = scale?.y ?: 1f get() = scale?.y ?: 1f
set(value) { set(value) {
scale().y = value.toDouble() scale().y = value.toDouble()
propertyChanged(Solid.Y_SCALE_KEY) notifyPropertyChanged(Solid.Y_SCALE_KEY)
} }
public var Solid.scaleZ: Number public var Solid.scaleZ: Number
get() = scale?.z ?: 1f get() = scale?.z ?: 1f
set(value) { set(value) {
scale().z = value.toDouble() scale().z = value.toDouble()
propertyChanged(Solid.Z_SCALE_KEY) notifyPropertyChanged(Solid.Z_SCALE_KEY)
} }

View File

@ -1,6 +1,6 @@
package hep.dataforge.vision.solid package hep.dataforge.vision.solid
import hep.dataforge.meta.Config import hep.dataforge.meta.MetaItem
import hep.dataforge.meta.descriptors.NodeDescriptor import hep.dataforge.meta.descriptors.NodeDescriptor
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.names.NameToken import hep.dataforge.names.NameToken
@ -87,7 +87,10 @@ public tailrec fun PrototypeHolder.getPrototype(name: Name): Solid? =
prototypes?.get(name) as? Solid ?: (parent as? PrototypeHolder)?.getPrototype(name) prototypes?.get(name) as? Solid ?: (parent as? PrototypeHolder)?.getPrototype(name)
@VisionBuilder @VisionBuilder
public fun VisionContainerBuilder<Vision>.group(name: Name = Name.EMPTY, action: SolidGroup.() -> Unit = {}): SolidGroup = public fun VisionContainerBuilder<Vision>.group(
name: Name = Name.EMPTY,
action: SolidGroup.() -> Unit = {},
): SolidGroup =
SolidGroup().apply(action).also { set(name, it) } SolidGroup().apply(action).also { set(name, it) }
/** /**
@ -104,15 +107,10 @@ internal class Prototypes(
children: Map<NameToken, Vision> = emptyMap(), children: Map<NameToken, Vision> = emptyMap(),
) : VisionGroupBase(), PrototypeHolder { ) : VisionGroupBase(), PrototypeHolder {
init { override var parent: VisionGroup? = null
this.childrenInternal.putAll(children)
}
override var properties: Config? private val _children = HashMap(children)
get() = null override val children: Map<NameToken, Vision> get() = _children
set(_) {
error("Can't define properties for prototypes block")
}
override val prototypes: MutableVisionGroup get() = this override val prototypes: MutableVisionGroup get() = this
@ -123,6 +121,21 @@ internal class Prototypes(
} }
} }
override fun getOwnProperty(name: Name): MetaItem<*>? = null
override fun getProperty(
name: Name,
inherit: Boolean,
includeStyles: Boolean,
includeDefaults: Boolean,
): MetaItem<*>? = null
override fun setProperty(name: Name, item: MetaItem<*>?) {
TODO("Not yet implemented")
}
override val descriptor: NodeDescriptor? = null
companion object : KSerializer<MutableVisionGroup> { companion object : KSerializer<MutableVisionGroup> {
private val mapSerializer: KSerializer<Map<NameToken, Vision>> = private val mapSerializer: KSerializer<Map<NameToken, Vision>> =

View File

@ -36,7 +36,7 @@ public class SolidManager(meta: Meta) : AbstractPlugin(meta) {
private fun PolymorphicModuleBuilder<Solid>.solids() { private fun PolymorphicModuleBuilder<Solid>.solids() {
subclass(SolidGroup.serializer()) subclass(SolidGroup.serializer())
subclass(SolidReference.serializer()) subclass(SolidReferenceGroup.serializer())
subclass(Composite.serializer()) subclass(Composite.serializer())
subclass(Tube.serializer()) subclass(Tube.serializer())
subclass(Box.serializer()) subclass(Box.serializer())

View File

@ -10,13 +10,10 @@ import hep.dataforge.values.Value
import hep.dataforge.values.ValueType import hep.dataforge.values.ValueType
import hep.dataforge.values.asValue import hep.dataforge.values.asValue
import hep.dataforge.values.string import hep.dataforge.values.string
import hep.dataforge.vision.Colors import hep.dataforge.vision.*
import hep.dataforge.vision.VisionBuilder
import hep.dataforge.vision.setProperty
import hep.dataforge.vision.solid.SolidMaterial.Companion.MATERIAL_COLOR_KEY import hep.dataforge.vision.solid.SolidMaterial.Companion.MATERIAL_COLOR_KEY
import hep.dataforge.vision.solid.SolidMaterial.Companion.MATERIAL_KEY import hep.dataforge.vision.solid.SolidMaterial.Companion.MATERIAL_KEY
import hep.dataforge.vision.solid.SolidMaterial.Companion.MATERIAL_OPACITY_KEY import hep.dataforge.vision.solid.SolidMaterial.Companion.MATERIAL_OPACITY_KEY
import hep.dataforge.vision.widgetType
@VisionBuilder @VisionBuilder
public class ColorAccessor(private val parent: MutableItemProvider, private val colorKey: Name) { public class ColorAccessor(private val parent: MutableItemProvider, private val colorKey: Name) {
@ -115,7 +112,7 @@ public class SolidMaterial : Scheme() {
} }
} }
public val Solid.color: ColorAccessor get() = ColorAccessor(config, MATERIAL_COLOR_KEY) public val Solid.color: ColorAccessor get() = ColorAccessor(properties, MATERIAL_COLOR_KEY)
public var Solid.material: SolidMaterial? public var Solid.material: SolidMaterial?
get() = getProperty(MATERIAL_KEY).node?.let { SolidMaterial.read(it) } get() = getProperty(MATERIAL_KEY).node?.let { SolidMaterial.read(it) }
@ -123,11 +120,11 @@ public var Solid.material: SolidMaterial?
@VisionBuilder @VisionBuilder
public fun Solid.material(builder: SolidMaterial.() -> Unit) { public fun Solid.material(builder: SolidMaterial.() -> Unit) {
val node = config[MATERIAL_KEY].node val node = properties.getItem(MATERIAL_KEY).node
if (node != null) { if (node != null) {
SolidMaterial.update(node, builder) SolidMaterial.update(node, builder)
} else { } else {
config[MATERIAL_KEY] = SolidMaterial(builder) setProperty(MATERIAL_KEY, SolidMaterial(builder))
} }
} }

View File

@ -1,156 +0,0 @@
package hep.dataforge.vision.solid
import hep.dataforge.meta.*
import hep.dataforge.meta.descriptors.NodeDescriptor
import hep.dataforge.names.*
import hep.dataforge.vision.*
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import kotlin.collections.set
public abstract class AbstractReference : SolidBase(), VisionGroup {
public abstract val prototype: Solid
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? = sequence {
yield(properties?.get(name))
yieldAll(getStyleItems(name))
yield(prototype.getProperty(name))
if (inherit) {
yield(parent?.getProperty(name, inherit))
}
}.merge()
override var styles: List<String>
get() = (properties[Vision.STYLE_KEY]?.stringList ?: emptyList()) + prototype.styles
set(value) {
config[Vision.STYLE_KEY] = value
}
override val allProperties: Laminate
get() = Laminate(
properties,
allStyles,
prototype.allProperties,
parent?.allProperties,
)
override fun attachChildren() {
//do nothing
}
override val descriptor: NodeDescriptor get() = prototype.descriptor
}
/**
* A reference [Solid] to reuse a template object
*/
@Serializable
@SerialName("solid.ref")
public class SolidReference(
public val templateName: Name,
) : AbstractReference(), Solid {
/**
* Recursively search for defined template in the parent
*/
override val prototype: Solid
get() = (parent as? SolidGroup)?.getPrototype(templateName)
?: error("Prototype with name $templateName not found in $parent")
@Transient
private val propertyCache: HashMap<Name, Config> = HashMap()
override val children: Map<NameToken, SolidReference.ReferenceChild>
get() = (prototype as? VisionGroup)?.children
?.filter { !it.key.toString().startsWith("@") }
?.mapValues {
ReferenceChild(it.key.asName())
} ?: emptyMap()
private fun childPropertyName(childName: Name, propertyName: Name): Name {
return NameToken(REFERENCE_CHILD_PROPERTY_PREFIX, childName.toString()) + propertyName
}
private fun prototypeFor(name: Name): Solid {
return (prototype as? SolidGroup)?.get(name) as? Solid
?: error("Prototype with name $name not found in $this")
}
//override fun findAllStyles(): Laminate = Laminate((styles + prototype.styles).mapNotNull { findStyle(it) })
/**
* A ProxyChild is created temporarily only to interact with properties, it does not store any values
* (properties are stored in external cache) and created and destroyed on-demand).
*/
public inner class ReferenceChild(public val name: Name) : AbstractReference() {
override val prototype: Solid get() = prototypeFor(name)
override val children: Map<NameToken, Vision>
get() = (prototype as? VisionGroup)?.children
?.filter { !it.key.toString().startsWith("@") }
?.mapValues { (key, _) ->
ReferenceChild(name + key.asName())
} ?: emptyMap()
override var properties: Config?
get() = propertyCache[name]
set(value) {
if (value == null) {
propertyCache.remove(name)?.also {
//Removing listener if it is present
removeChangeListener(this@SolidReference)
}
} else {
propertyCache[name] = value.also {
onPropertyChange(this@SolidReference) { propertyName ->
this@SolidReference.propertyChanged(childPropertyName(name, propertyName))
}
}
}
}
}
public companion object {
public const val REFERENCE_CHILD_PROPERTY_PREFIX: String = "@child"
}
}
/**
* Get a vision prototype if it is a [SolidReference] or vision itself if it is not
*/
public val Vision.prototype: Vision
get() = when (this) {
is AbstractReference -> prototype
else -> this
}
/**
* Create ref for existing prototype
*/
public fun SolidGroup.ref(
templateName: Name,
name: String = "",
): SolidReference = SolidReference(templateName).also { set(name, it) }
/**
* Add new [SolidReference] wrapping given object and automatically adding it to the prototypes
*/
public fun SolidGroup.ref(
name: String,
obj: Solid,
templateName: Name = name.toName(),
): SolidReference {
val existing = getPrototype(templateName)
if (existing == null) {
prototypes {
this[templateName] = obj
}
} else if (existing != obj) {
error("Can't add different prototype on top of existing one")
}
return this@ref.ref(templateName, name)
}

View File

@ -0,0 +1,189 @@
package hep.dataforge.vision.solid
import hep.dataforge.meta.*
import hep.dataforge.meta.descriptors.NodeDescriptor
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.serialization.SerialName
import kotlinx.serialization.Serializable
public interface SolidReference : Vision {
public val prototype: Solid
}
/**
* A reference [Solid] to reuse a template object
*/
@Serializable
@SerialName("solid.ref")
public class SolidReferenceGroup(
public val templateName: Name,
) : SolidBase(), SolidReference, VisionGroup {
/**
* Recursively search for defined template in the parent
*/
override val prototype: Solid
get() = (parent as? SolidGroup)?.getPrototype(templateName)
?: error("Prototype with name $templateName not found in $parent")
override val children: Map<NameToken, Vision>
get() = (prototype as? VisionGroup)?.children
?.filter { !it.key.toString().startsWith("@") }
?.mapValues {
ReferenceChild(it.key.asName())
} ?: emptyMap()
private fun childToken(childName: Name): NameToken =
NameToken(REFERENCE_CHILD_PROPERTY_PREFIX, childName.toString())
private fun childPropertyName(childName: Name, propertyName: Name): Name =
childToken(childName) + propertyName
private fun getChildProperty(childName: Name, propertyName: Name): MetaItem<*>? {
return getOwnProperty(childPropertyName(childName, propertyName))
}
private fun setChildProperty(childName: Name, propertyName: Name, item: MetaItem<*>?) {
setProperty(childPropertyName(childName, propertyName), item)
}
private fun prototypeFor(name: Name): Solid {
return if(name.isEmpty()) prototype else {
(prototype as? SolidGroup)?.get(name) as? Solid
?: error("Prototype with name $name not found in $this")
}
}
override fun getProperty(
name: Name,
inherit: Boolean,
includeStyles: Boolean,
includeDefaults: Boolean,
): MetaItem<*>? = sequence {
yield(getOwnProperty(name))
if (includeStyles) {
yieldAll(getStyleItems(name))
}
yield(prototype.getProperty(name, inherit, includeStyles, includeDefaults))
if (inherit) {
yield(parent?.getProperty(name, inherit))
}
}.merge()
override fun attachChildren() {
//do nothing
}
override val descriptor: NodeDescriptor get() = prototype.descriptor
/**
* A ProxyChild is created temporarily only to interact with properties, it does not store any values
* (properties are stored in external cache) and created and destroyed on-demand).
*/
private inner class ReferenceChild(private val childName: Name) : SolidReference, VisionGroup {
override val prototype: Solid get() = prototypeFor(childName)
override val children: Map<NameToken, Vision>
get() = (prototype as? VisionGroup)?.children
?.filter { !it.key.toString().startsWith("@") }
?.mapValues { (key, _) ->
ReferenceChild(childName + key.asName())
} ?: emptyMap()
override fun getOwnProperty(name: Name): MetaItem<*>? = getChildProperty(childName, name)
override fun setProperty(name: Name, item: MetaItem<*>?) {
setChildProperty(childName, name, item)
}
override fun getProperty(
name: Name,
inherit: Boolean,
includeStyles: Boolean,
includeDefaults: Boolean,
): MetaItem<*>? = sequence {
yield(getOwnProperty(name))
if (includeStyles) {
yieldAll(getStyleItems(name))
}
yield(prototype.getProperty(name, inherit, includeStyles, includeDefaults))
if (inherit) {
yield(parent?.getProperty(name, inherit))
}
}.merge()
override var parent: VisionGroup?
get(){
val parentName = childName.cutLast()
return if( parentName.isEmpty()) this@SolidReferenceGroup else ReferenceChild(parentName)
}
set(value) {
error("Setting a parent for a reference child is not possible")
}
override val propertyInvalidated: Flow<Name>
get() = this@SolidReferenceGroup.propertyInvalidated.filter { name ->
name.startsWith(childToken(childName))
}.map { name ->
name.cutFirst()
}
override fun notifyPropertyChanged(propertyName: Name) {
this@SolidReferenceGroup.notifyPropertyChanged(childPropertyName(childName, propertyName))
}
override fun update(change: VisionChange) {
TODO("Not yet implemented")
}
override fun attachChildren() {
//do nothing
}
override val descriptor: NodeDescriptor get() = prototype.descriptor
}
public companion object {
public const val REFERENCE_CHILD_PROPERTY_PREFIX: String = "@child"
}
}
/**
* Get a vision prototype if it is a [SolidReferenceGroup] or vision itself if it is not
*/
public val Vision.prototype: Vision
get() = if (this is SolidReference) prototype else this
/**
* Create ref for existing prototype
*/
public fun SolidGroup.ref(
templateName: Name,
name: String = "",
): SolidReferenceGroup = SolidReferenceGroup(templateName).also { set(name, it) }
/**
* Add new [SolidReferenceGroup] wrapping given object and automatically adding it to the prototypes
*/
public fun SolidGroup.ref(
name: String,
obj: Solid,
templateName: Name = name.toName(),
): SolidReferenceGroup {
val existing = getPrototype(templateName)
if (existing == null) {
prototypes {
this[templateName] = obj
}
} else if (existing != obj) {
error("Can't add different prototype on top of existing one")
}
return this@ref.ref(templateName, name)
}

View File

@ -1,18 +1,15 @@
package hep.dataforge.vision.solid.transform package hep.dataforge.vision.solid.transform
import hep.dataforge.meta.DFExperimental import hep.dataforge.meta.DFExperimental
import hep.dataforge.meta.update
import hep.dataforge.names.asName import hep.dataforge.names.asName
import hep.dataforge.vision.MutableVisionGroup import hep.dataforge.vision.*
import hep.dataforge.vision.Vision
import hep.dataforge.vision.VisionGroup
import hep.dataforge.vision.solid.* import hep.dataforge.vision.solid.*
@DFExperimental @DFExperimental
internal fun mergeChild(parent: VisionGroup, child: Vision): Vision { internal fun mergeChild(parent: VisionGroup, child: Vision): Vision {
return child.apply { return child.apply {
config.update(parent.config) configure(parent.ownProperties)
//parent.properties?.let { config.update(it) } //parent.properties?.let { config.update(it) }
@ -40,7 +37,7 @@ internal object RemoveSingleChild : VisualTreeTransform<SolidGroup>() {
override fun SolidGroup.transformInPlace() { override fun SolidGroup.transformInPlace() {
fun MutableVisionGroup.replaceChildren() { fun MutableVisionGroup.replaceChildren() {
children.forEach { (childName, parent) -> children.forEach { (childName, parent) ->
if (parent is SolidReference) return@forEach //ignore refs if (parent is SolidReferenceGroup) return@forEach //ignore refs
if (parent is MutableVisionGroup) { if (parent is MutableVisionGroup) {
parent.replaceChildren() parent.replaceChildren()
} }

View File

@ -6,7 +6,7 @@ import hep.dataforge.names.asName
import hep.dataforge.vision.MutableVisionGroup import hep.dataforge.vision.MutableVisionGroup
import hep.dataforge.vision.VisionGroup import hep.dataforge.vision.VisionGroup
import hep.dataforge.vision.solid.SolidGroup import hep.dataforge.vision.solid.SolidGroup
import hep.dataforge.vision.solid.SolidReference import hep.dataforge.vision.solid.SolidReferenceGroup
@DFExperimental @DFExperimental
internal object UnRef : VisualTreeTransform<SolidGroup>() { internal object UnRef : VisualTreeTransform<SolidGroup>() {
@ -17,7 +17,7 @@ internal object UnRef : VisualTreeTransform<SolidGroup>() {
counter.forEach { (key, value) -> counter.forEach { (key, value) ->
reducer[key] = (reducer[key] ?: 0) + value reducer[key] = (reducer[key] ?: 0) + value
} }
} else if (obj is SolidReference) { } else if (obj is SolidReferenceGroup) {
reducer[obj.templateName] = (reducer[obj.templateName] ?: 0) + 1 reducer[obj.templateName] = (reducer[obj.templateName] ?: 0) + 1
} }
@ -27,8 +27,8 @@ internal object UnRef : VisualTreeTransform<SolidGroup>() {
private fun MutableVisionGroup.unref(name: Name) { private fun MutableVisionGroup.unref(name: Name) {
(this as? SolidGroup)?.prototypes?.set(name, null) (this as? SolidGroup)?.prototypes?.set(name, null)
children.filter { (it.value as? SolidReference)?.templateName == name }.forEach { (key, value) -> children.filter { (it.value as? SolidReferenceGroup)?.templateName == name }.forEach { (key, value) ->
val reference = value as SolidReference val reference = value as SolidReferenceGroup
val newChild = mergeChild(reference, reference.prototype) val newChild = mergeChild(reference, reference.prototype)
newChild.parent = null newChild.parent = null
set(key.asName(), newChild) // replace proxy with merged object set(key.asName(), newChild) // replace proxy with merged object

View File

@ -1,8 +1,8 @@
package hep.dataforge.vision.solid package hep.dataforge.vision.solid
import hep.dataforge.meta.int import hep.dataforge.meta.int
import hep.dataforge.meta.set
import hep.dataforge.names.asName import hep.dataforge.names.asName
import hep.dataforge.vision.setProperty
import hep.dataforge.vision.styleSheet import hep.dataforge.vision.styleSheet
import hep.dataforge.vision.useStyle import hep.dataforge.vision.useStyle
import kotlin.test.Test import kotlin.test.Test
@ -14,7 +14,7 @@ class PropertyTest {
fun testInheritedProperty() { fun testInheritedProperty() {
var box: Box? = null var box: Box? = null
val group = SolidGroup().apply { val group = SolidGroup().apply {
config["test"] = 22 setProperty("test", 22)
group { group {
box = box(100, 100, 100) box = box(100, 100, 100)
} }
@ -60,7 +60,7 @@ class PropertyTest {
@Test @Test
fun testReferenceStyleProperty() { fun testReferenceStyleProperty() {
var box: SolidReference? = null var box: SolidReferenceGroup? = null
val group = SolidGroup{ val group = SolidGroup{
styleSheet { styleSheet {
set("testStyle") { set("testStyle") {

View File

@ -4,6 +4,7 @@ import hep.dataforge.names.Name
import hep.dataforge.names.toName import hep.dataforge.names.toName
import hep.dataforge.vision.MutableVisionGroup import hep.dataforge.vision.MutableVisionGroup
import hep.dataforge.vision.get import hep.dataforge.vision.get
import hep.dataforge.vision.ownProperties
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
@ -15,7 +16,7 @@ fun SolidGroup.refGroup(
name: String, name: String,
templateName: Name = name.toName(), templateName: Name = name.toName(),
block: MutableVisionGroup.() -> Unit block: MutableVisionGroup.() -> Unit
): SolidReference { ): SolidReferenceGroup {
val group = SolidGroup().apply(block) val group = SolidGroup().apply(block)
return ref(name, group, templateName) return ref(name, group, templateName)
} }
@ -32,7 +33,7 @@ class SerializationTest {
val string = SolidManager.encodeToString(cube) val string = SolidManager.encodeToString(cube)
println(string) println(string)
val newCube = SolidManager.decodeFromString(string) val newCube = SolidManager.decodeFromString(string)
assertEquals(cube.config, newCube.config) assertEquals(cube.ownProperties, newCube.ownProperties)
} }
@Test @Test
@ -53,7 +54,7 @@ class SerializationTest {
val string = SolidManager.encodeToString(group) val string = SolidManager.encodeToString(group)
println(string) println(string)
val reconstructed = SolidManager.decodeFromString(string) as SolidGroup val reconstructed = SolidManager.decodeFromString(string) as SolidGroup
assertEquals(group["cube"]?.config, reconstructed["cube"]?.config) assertEquals(group["cube"]?.ownProperties, reconstructed["cube"]?.ownProperties)
} }
} }

View File

@ -45,7 +45,7 @@ class VisionUpdateTest {
val serialized = visionManager.jsonFormat.encodeToString(VisionChange.serializer(), change) val serialized = visionManager.jsonFormat.encodeToString(VisionChange.serializer(), change)
println(serialized) println(serialized)
val reconstructed = visionManager.jsonFormat.decodeFromString(VisionChange.serializer(), serialized) val reconstructed = visionManager.jsonFormat.decodeFromString(VisionChange.serializer(), serialized)
assertEquals(change.propertyChange,reconstructed.propertyChange) assertEquals(change.properties,reconstructed.properties)
} }
@Test @Test

View File

@ -15,20 +15,22 @@ 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
/** /**
* Basic geometry-based factory * Basic geometry-based factory
*/ */
public abstract class MeshThreeFactory<in T : Solid>( public abstract class MeshThreeFactory<in T : Solid>(
override val type: KClass<in T> override val type: KClass<in T>,
) : ThreeFactory<T> { ) : ThreeFactory<T> {
/** /**
* Build a geometry for an object * Build a geometry for an object
*/ */
public abstract fun buildGeometry(obj: T): BufferGeometry public abstract fun buildGeometry(obj: T): BufferGeometry
override fun invoke(obj: T): Mesh { override fun invoke(three: ThreePlugin, obj: T): Mesh {
val geometry = buildGeometry(obj) val geometry = buildGeometry(obj)
//JS sometimes tries to pass Geometry as BufferGeometry //JS sometimes tries to pass Geometry as BufferGeometry
@ -43,7 +45,7 @@ public abstract class MeshThreeFactory<in T : Solid>(
}.applyProperties(obj) }.applyProperties(obj)
//add listener to object properties //add listener to object properties
obj.onPropertyChange(this) { name -> obj.propertyInvalidated.onEach { 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
@ -57,7 +59,8 @@ 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
} }
@ -72,7 +75,7 @@ public abstract class MeshThreeFactory<in T : Solid>(
} }
} }
fun Mesh.applyProperties(obj: Solid): Mesh = apply{ internal fun Mesh.applyProperties(obj: Solid): Mesh = apply {
material = getMaterial(obj, true) material = getMaterial(obj, true)
applyEdges(obj) applyEdges(obj)
applyWireFrame(obj) applyWireFrame(obj)
@ -82,7 +85,7 @@ fun Mesh.applyProperties(obj: Solid): Mesh = apply{
} }
} }
fun Mesh.applyEdges(obj: Solid) { internal fun Mesh.applyEdges(obj: Solid) {
val edges = children.find { it.name == "@edges" } as? LineSegments val edges = children.find { it.name == "@edges" } as? LineSegments
//inherited edges definition, enabled by default //inherited edges definition, enabled by default
if (obj.getProperty(MeshThreeFactory.EDGES_ENABLED_KEY).boolean != false) { if (obj.getProperty(MeshThreeFactory.EDGES_ENABLED_KEY).boolean != false) {
@ -108,7 +111,7 @@ fun Mesh.applyEdges(obj: Solid) {
} }
} }
fun Mesh.applyWireFrame(obj: Solid) { internal fun Mesh.applyWireFrame(obj: Solid) {
children.find { it.name == "@wireframe" }?.let { children.find { it.name == "@wireframe" }?.let {
remove(it) remove(it)
(it as LineSegments).dispose() (it as LineSegments).dispose()
@ -116,7 +119,8 @@ fun Mesh.applyWireFrame(obj: Solid) {
//inherited wireframe definition, disabled by default //inherited wireframe definition, disabled by default
if (obj.getProperty(MeshThreeFactory.WIREFRAME_ENABLED_KEY).boolean == true) { if (obj.getProperty(MeshThreeFactory.WIREFRAME_ENABLED_KEY).boolean == true) {
val bufferGeometry = geometry as? BufferGeometry ?: return val bufferGeometry = geometry as? BufferGeometry ?: return
val material = ThreeMaterials.getLineMaterial(obj.getProperty(MeshThreeFactory.WIREFRAME_MATERIAL_KEY).node, true) val material =
ThreeMaterials.getLineMaterial(obj.getProperty(MeshThreeFactory.WIREFRAME_MATERIAL_KEY).node, true)
add( add(
LineSegments( LineSegments(
WireframeGeometry(bufferGeometry), WireframeGeometry(bufferGeometry),

View File

@ -2,11 +2,10 @@ package hep.dataforge.vision.solid.three
import hep.dataforge.vision.solid.Convex import hep.dataforge.vision.solid.Convex
import info.laht.threekt.external.geometries.ConvexBufferGeometry import info.laht.threekt.external.geometries.ConvexBufferGeometry
import info.laht.threekt.math.Vector3
public object ThreeConvexFactory : MeshThreeFactory<Convex>(Convex::class) { public object ThreeConvexFactory : MeshThreeFactory<Convex>(Convex::class) {
override fun buildGeometry(obj: Convex): ConvexBufferGeometry { override fun buildGeometry(obj: Convex): ConvexBufferGeometry {
@Suppress("USELESS_CAST") val vectors = obj.points.toTypedArray() as Array<Vector3> val vectors = obj.points.map { it.toVector() }.toTypedArray()
return ConvexBufferGeometry(vectors) return ConvexBufferGeometry(vectors)
} }
} }

View File

@ -22,7 +22,7 @@ public interface ThreeFactory<in T : Vision> {
public val type: KClass<in T> public val type: KClass<in T>
public operator fun invoke(obj: T): Object3D public operator fun invoke(three: ThreePlugin, obj: T): Object3D
public companion object { public companion object {
public const val TYPE: String = "threeFactory" public const val TYPE: String = "threeFactory"

View File

@ -1,12 +1,15 @@
package hep.dataforge.vision.solid.three package hep.dataforge.vision.solid.three
import hep.dataforge.context.logger
import hep.dataforge.vision.solid.SolidLabel import hep.dataforge.vision.solid.SolidLabel
import hep.dataforge.vision.solid.three.ThreeMaterials.getMaterial import hep.dataforge.vision.solid.three.ThreeMaterials.getMaterial
import info.laht.threekt.core.Object3D import info.laht.threekt.core.Object3D
import info.laht.threekt.geometries.TextBufferGeometry import info.laht.threekt.geometries.TextBufferGeometry
import info.laht.threekt.objects.Mesh import info.laht.threekt.objects.Mesh
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
/** /**
@ -15,7 +18,7 @@ import kotlin.reflect.KClass
public object ThreeLabelFactory : ThreeFactory<SolidLabel> { public object ThreeLabelFactory : ThreeFactory<SolidLabel> {
override val type: KClass<in SolidLabel> get() = SolidLabel::class override val type: KClass<in SolidLabel> get() = SolidLabel::class
override fun invoke(obj: SolidLabel): Object3D { override fun invoke(three: ThreePlugin, obj: SolidLabel): Object3D {
val textGeo = TextBufferGeometry(obj.text, jsObject { val textGeo = TextBufferGeometry(obj.text, jsObject {
font = obj.fontFamily font = obj.fontFamily
size = 20 size = 20
@ -24,9 +27,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.onPropertyChange(this@ThreeLabelFactory) { _ -> obj.propertyInvalidated.onEach { _ ->
//TODO //TODO
} three.logger.warn{"Label parameter change not implemented"}
}.launchIn(three.updateScope)
} }
} }
} }

View File

@ -9,12 +9,14 @@ 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> {
override val type: KClass<PolyLine> get() = PolyLine::class override val type: KClass<PolyLine> get() = PolyLine::class
override fun invoke(obj: PolyLine): Object3D { override fun invoke(three: ThreePlugin, obj: PolyLine): Object3D {
val geometry = Geometry().apply { val geometry = Geometry().apply {
vertices = Array(obj.points.size) { obj.points[it].toVector() } vertices = Array(obj.points.size) { obj.points[it].toVector() }
} }
@ -28,9 +30,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.onPropertyChange(this) { propertyName -> obj.propertyInvalidated.onEach { propertyName ->
updateProperty(obj, propertyName) updateProperty(obj, propertyName)
} }.launchIn(three.updateScope)
} }
} }

View File

@ -9,6 +9,9 @@ import hep.dataforge.vision.solid.*
import hep.dataforge.vision.solid.specifications.Canvas3DOptions import hep.dataforge.vision.solid.specifications.Canvas3DOptions
import hep.dataforge.vision.visible import hep.dataforge.vision.visible
import info.laht.threekt.core.Object3D import info.laht.threekt.core.Object3D
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.w3c.dom.Element import org.w3c.dom.Element
import org.w3c.dom.HTMLElement import org.w3c.dom.HTMLElement
import kotlin.collections.set import kotlin.collections.set
@ -24,6 +27,9 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
private val compositeFactory = ThreeCompositeFactory(this) private val compositeFactory = ThreeCompositeFactory(this)
private val refFactory = ThreeReferenceFactory(this) private val refFactory = ThreeReferenceFactory(this)
//TODO generate a separate supervisor update scope
internal val updateScope: CoroutineScope get() = context
init { init {
//Add specialized factories here //Add specialized factories here
objectFactories[Box::class] = ThreeBoxFactory objectFactories[Box::class] = ThreeBoxFactory
@ -44,7 +50,7 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
public fun buildObject3D(obj: Solid): Object3D { public fun buildObject3D(obj: Solid): Object3D {
return when (obj) { return when (obj) {
is ThreeVision -> obj.render() is ThreeVision -> obj.render()
is SolidReference -> refFactory(obj) is SolidReferenceGroup -> refFactory(obj)
is SolidGroup -> { is SolidGroup -> {
val group = ThreeGroup() val group = ThreeGroup()
obj.children.forEach { (token, child) -> obj.children.forEach { (token, child) ->
@ -63,7 +69,7 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
updatePosition(obj) updatePosition(obj)
//obj.onChildrenChange() //obj.onChildrenChange()
obj.onPropertyChange(this) { name -> obj.propertyInvalidated.onEach { name ->
if ( if (
name.startsWith(Solid.POSITION_KEY) || name.startsWith(Solid.POSITION_KEY) ||
name.startsWith(Solid.ROTATION) || name.startsWith(Solid.ROTATION) ||
@ -74,9 +80,9 @@ 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.onStructureChange(this) { nameToken, _, child -> obj.structureChanges.onEach { (nameToken, _, child) ->
// if (name.isEmpty()) { // if (name.isEmpty()) {
// logger.error { "Children change with empty name on $group" } // logger.error { "Children change with empty name on $group" }
// return@onChildrenChange // return@onChildrenChange
@ -99,7 +105,7 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
logger.error(ex) { "Failed to render $child" } logger.error(ex) { "Failed to render $child" }
} }
} }
} }.launchIn(updateScope)
} }
} }
is Composite -> compositeFactory(obj) is Composite -> compositeFactory(obj)

View File

@ -4,17 +4,19 @@ import hep.dataforge.names.cutFirst
import hep.dataforge.names.firstOrNull import hep.dataforge.names.firstOrNull
import hep.dataforge.names.toName import hep.dataforge.names.toName
import hep.dataforge.vision.solid.Solid import hep.dataforge.vision.solid.Solid
import hep.dataforge.vision.solid.SolidReference import hep.dataforge.vision.solid.SolidReferenceGroup
import hep.dataforge.vision.solid.SolidReference.Companion.REFERENCE_CHILD_PROPERTY_PREFIX import hep.dataforge.vision.solid.SolidReferenceGroup.Companion.REFERENCE_CHILD_PROPERTY_PREFIX
import info.laht.threekt.core.BufferGeometry import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.Object3D import info.laht.threekt.core.Object3D
import info.laht.threekt.objects.Mesh import info.laht.threekt.objects.Mesh
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlin.reflect.KClass import kotlin.reflect.KClass
public class ThreeReferenceFactory(public val three: ThreePlugin) : ThreeFactory<SolidReference> { public class ThreeReferenceFactory(public val three: ThreePlugin) : ThreeFactory<SolidReferenceGroup> {
private val cache = HashMap<Solid, Object3D>() private val cache = HashMap<Solid, Object3D>()
override val type: KClass<SolidReference> = SolidReference::class override val type: KClass<SolidReferenceGroup> = SolidReferenceGroup::class
private fun Object3D.replicate(): Object3D { private fun Object3D.replicate(): Object3D {
return when (this) { return when (this) {
@ -30,7 +32,7 @@ public class ThreeReferenceFactory(public val three: ThreePlugin) : ThreeFactory
} }
} }
override fun invoke(obj: SolidReference): Object3D { override fun invoke(obj: SolidReferenceGroup): Object3D {
val template = obj.prototype val template = obj.prototype
val cachedObject = cache.getOrPut(template) { val cachedObject = cache.getOrPut(template) {
three.buildObject3D(template) three.buildObject3D(template)
@ -43,7 +45,9 @@ public class ThreeReferenceFactory(public val three: ThreePlugin) : ThreeFactory
object3D.applyProperties(obj) object3D.applyProperties(obj)
} }
obj.onPropertyChange(this) { name -> //TODO apply child properties
obj.propertyInvalidated.onEach { 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()
@ -53,7 +57,8 @@ public class ThreeReferenceFactory(public val three: ThreePlugin) : ThreeFactory
} else { } else {
object3D.updateProperty(obj, name) object3D.updateProperty(obj, name)
} }
} }.launchIn(three.updateScope)
return object3D return object3D
} }