Compare commits

...

3 Commits

40 changed files with 357 additions and 213 deletions

View File

@ -4,6 +4,7 @@
### Added
- Context receivers flag
- MeshLine for thick lines
- Custom client-side events and thier processing in VisionServer
### Changed
- Color accessor property is now `colorProperty`. Color uses `invoke` instead of `set`

View File

@ -3,6 +3,7 @@ package ru.mipt.npm.root
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.double
import space.kscience.dataforge.meta.int
import space.kscience.dataforge.meta.set
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.parseAsName
import space.kscience.dataforge.names.plus
@ -12,7 +13,6 @@ import space.kscience.kmath.geometry.fromRotationMatrix
import space.kscience.kmath.linear.VirtualMatrix
import space.kscience.visionforge.MutableVisionContainer
import space.kscience.visionforge.isEmpty
import space.kscience.visionforge.set
import space.kscience.visionforge.solid.*
import space.kscience.visionforge.solid.SolidMaterial.Companion.MATERIAL_COLOR_KEY
import kotlin.math.PI
@ -188,7 +188,7 @@ private fun SolidGroup.addShape(
}
"TGeoPgon" -> {
//TODO add a inner polygone layer
val fDphi by shape.meta.double(0.0)
val fNz by shape.meta.int(2)
val fPhi1 by shape.meta.double(360.0)
@ -201,19 +201,26 @@ private fun SolidGroup.addShape(
val startphi = degToRad(fPhi1)
val deltaphi = degToRad(fDphi)
extruded(name) {
fun Shape2DBuilder.pGon(radius: Double){
(0..<fNedges).forEach {
val phi = deltaphi / fNedges * it + startphi
point(radius * cos(phi), radius * sin(phi))
}
}
surface(name) {
//getting the radius of first
require(fNz > 1) { "The polyhedron geometry requires at least two planes" }
val baseRadius = fRmax[0]
shape {
(0..<fNedges).forEach {
val phi = deltaphi / fNedges * it + startphi
point(baseRadius * cos(phi), baseRadius * sin(phi))
}
}
(0 until fNz).forEach { index ->
//scaling all radii relative to first layer radius
layer(fZ[index], scale = fRmax[index] / baseRadius)
for (index in 0 until fNz){
layer(
fZ[index],
innerBuilder = {
pGon(fRmin[index])
},
outerBuilder = {
pGon(fRmax[index])
}
)
}
}.apply(block)
}

View File

@ -12,6 +12,7 @@ import kotlinx.serialization.modules.polymorphic
import kotlinx.serialization.modules.subclass
@Suppress("UNUSED_PARAMETER")
private fun <T> jsonRootDeserializer(
tSerializer: KSerializer<T>,
builder: (JsonElement) -> T,
@ -83,7 +84,7 @@ private object RootDecoder {
return ref.getOrPutValue {
// println("Decoding $it")
val actualTypeName = it.jsonObject["_typename"]?.jsonPrimitive?.content
// val actualTypeName = it.jsonObject["_typename"]?.jsonPrimitive?.content
input.json.decodeFromJsonElement(tSerializer, it)
}
}

View File

@ -34,6 +34,6 @@ class GDMLVisionTest {
val child = cubes[Name.of("composite-000","segment-0")]
assertNotNull(child)
child.properties.setValue(SolidMaterial.MATERIAL_COLOR_KEY, "red".asValue())
assertEquals("red", child.properties.getProperty(SolidMaterial.MATERIAL_COLOR_KEY).string)
assertEquals("red", child.properties.getMeta(SolidMaterial.MATERIAL_COLOR_KEY).string)
}
}

View File

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

View File

@ -75,7 +75,7 @@ fun main() = serve {
val incRot = Quaternion.fromRotation(30.degrees, Euclidean3DSpace.zAxis)
val rotationJob = context.launch {
context.launch {
var time: Long = 0L
while (isActive) {
with(QuaternionField) {

View File

@ -12,9 +12,10 @@ fun main() = makeVisionFile {
extruded("extruded") {
shape{
polygon(8, 100)
layer(-30)
layer(30)
}
layer(-30)
layer(0, x = 10, y = 10)
layer(30)
}
}
}

View File

@ -0,0 +1,19 @@
package space.kscience.visionforge.examples
import space.kscience.visionforge.solid.ambientLight
import space.kscience.visionforge.solid.polygon
import space.kscience.visionforge.solid.solid
import space.kscience.visionforge.solid.surface
fun main() = makeVisionFile {
vision("canvas") {
solid {
ambientLight()
surface("surface") {
layer(0, { polygon(8, 10) }, { polygon(8, 20) })
layer(10, { polygon(8, 20) }, { polygon(8, 30) })
layer(20, { polygon(8, 10) }, { polygon(8, 20) })
}
}
}
}

View File

@ -1,7 +1,7 @@
kotlin.code.style=official
kotlin.mpp.stability.nowarn=true
kotlin.js.compiler=ir
kotlin.incremental.js.ir=true
#kotlin.incremental.js.ir=true
org.gradle.parallel=true
org.gradle.jvmargs=-Xmx4G

View File

@ -3,7 +3,7 @@ package space.kscience.visionforge
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.MutableMeta
import space.kscience.dataforge.meta.descriptors.MetaDescriptor

View File

@ -13,7 +13,7 @@ import kotlin.jvm.JvmInline
@JvmInline
public value class StyleSheet(private val owner: Vision) {
private val styleNode: Meta get() = owner.properties.getProperty(STYLESHEET_KEY)
private val styleNode: Meta get() = owner.properties.getMeta(STYLESHEET_KEY)
public val items: Map<NameToken, Meta> get() = styleNode.items
@ -23,7 +23,7 @@ public value class StyleSheet(private val owner: Vision) {
* Define a style without notifying owner
*/
public fun define(key: String, style: Meta?) {
owner.properties.setProperty(STYLESHEET_KEY + key, style)
owner.properties.setMeta(STYLESHEET_KEY + key, style)
}
/**

View File

@ -37,6 +37,9 @@ public interface Vision : Described {
* Update this vision using a dif represented by [VisionChange].
*/
public fun update(change: VisionChange) {
if (change.children?.isNotEmpty() == true) {
error("Vision is not a group")
}
change.properties?.let {
updateProperties(it, Name.EMPTY)
}
@ -67,7 +70,7 @@ public var Vision.visible: Boolean?
*/
public fun Vision.onPropertyChange(
scope: CoroutineScope? = manager?.context,
callback: suspend (Name) -> Unit
callback: suspend (Name) -> Unit,
): Job = properties.changes.onEach {
callback(it)
}.launchIn(scope ?: error("Orphan Vision can't observe properties"))

View File

@ -1,15 +1,15 @@
package space.kscience.visionforge
import kotlinx.coroutines.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.serialization.Serializable
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.isEmpty
import space.kscience.dataforge.names.plus
@ -27,22 +27,6 @@ private fun Vision.deepCopy(manager: VisionManager): Vision {
return manager.decodeFromJson(json)
}
/**
* A vision used only in change propagation and showing that the target should be removed
*/
@Serializable
public object NullVision : Vision {
override var parent: Vision?
get() = null
set(_) {
error("Can't set parent for null vision")
}
override val properties: MutableVisionProperties get() = error("Can't get properties of `NullVision`")
override val descriptor: MetaDescriptor? = null
}
/**
* An update for a [Vision]
@ -111,18 +95,6 @@ public class VisionChangeBuilder : MutableVisionContainer<Vision> {
)
}
/**
* @param vision a new value for vision content. If the Vision is to be removed should be [NullVision]
* @param properties updated properties
* @param children a map of children changed in ths [VisionChange]. If a child to be removed, set [delete] flag to true.
*/
@Serializable
public data class VisionChange(
public val vision: Vision? = null,
public val properties: Meta? = null,
public val children: Map<Name, VisionChange>? = null,
)
public inline fun VisionManager.VisionChange(block: VisionChangeBuilder.() -> Unit): VisionChange =
VisionChangeBuilder().apply(block).deepCopy(this)

View File

@ -0,0 +1,52 @@
package space.kscience.visionforge
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.names.Name
/**
* An event propagated from client to a server
*/
@Serializable
public sealed interface VisionEvent
/**
* An event that consists of custom meta
*/
@Serializable
@SerialName("meta")
public class VisionMetaEvent(public val targetName: Name, public val meta: Meta) : VisionEvent
/**
* A vision used only in change propagation and showing that the target should be removed
*/
@Serializable
@SerialName("null")
public object NullVision : Vision {
override var parent: Vision?
get() = null
set(_) {
error("Can't set parent for null vision")
}
override val properties: MutableVisionProperties get() = error("Can't get properties of `NullVision`")
override val descriptor: MetaDescriptor? = null
}
/**
* @param vision a new value for vision content. If the Vision is to be removed should be [NullVision]
* @param properties updated properties
* @param children a map of children changed in ths [VisionChange].
*/
@Serializable
@SerialName("change")
public data class VisionChange(
public val vision: Vision? = null,
public val properties: Meta? = null,
public val children: Map<Name, VisionChange>? = null,
) : VisionEvent

View File

@ -10,11 +10,25 @@ import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.parseAsName
import space.kscience.dataforge.names.plus
import space.kscience.visionforge.AbstractVisionGroup.Companion.updateProperties
import space.kscience.visionforge.Vision.Companion.STYLE_KEY
public interface VisionGroup : Vision {
public val children: VisionChildren
override fun update(change: VisionChange) {
change.children?.forEach { (name, change) ->
if (change.vision != null || change.vision == NullVision) {
error("VisionGroup is read-only")
} else {
children.getChild(name)?.update(change)
}
}
change.properties?.let {
updateProperties(it, Name.EMPTY)
}
}
}
public interface MutableVisionGroup : VisionGroup {
@ -22,15 +36,6 @@ public interface MutableVisionGroup : VisionGroup {
override val children: MutableVisionChildren
public fun createGroup(): MutableVisionGroup
}
public val Vision.children: VisionChildren? get() = (this as? VisionGroup)?.children
/**
* A full base implementation for a [Vision]
*/
@Serializable
public abstract class AbstractVisionGroup : AbstractVision(), MutableVisionGroup {
override fun update(change: VisionChange) {
change.children?.forEach { (name, change) ->
@ -44,6 +49,15 @@ public abstract class AbstractVisionGroup : AbstractVision(), MutableVisionGroup
updateProperties(it, Name.EMPTY)
}
}
}
public val Vision.children: VisionChildren? get() = (this as? VisionGroup)?.children
/**
* A full base implementation for a [Vision]
*/
@Serializable
public abstract class AbstractVisionGroup : AbstractVision(), MutableVisionGroup {
@SerialName("children")
protected var childrenInternal: MutableMap<NameToken, Vision>? = null

View File

@ -12,7 +12,7 @@ import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.get
import space.kscience.dataforge.names.*
public interface VisionProperties {
public interface VisionProperties : MetaProvider {
/**
* Raw Visions own properties without styles, defaults, etc.
@ -23,21 +23,26 @@ public interface VisionProperties {
public fun getValue(
name: Name,
inherit: Boolean? = null,
inherit: Boolean?,
includeStyles: Boolean? = null,
): Value?
override fun getValue(name: Name): Value? = getValue(name, null, null)
/**
* Get property with given layer flags.
* @param inherit toggles parent node property lookup. Null means inference from descriptor.
* @param includeStyles toggles inclusion of properties from styles.
*/
public fun getProperty(
public fun getMeta(
name: Name,
inherit: Boolean? = null,
inherit: Boolean?,
includeStyles: Boolean? = null,
): Meta
override fun getMeta(name: Name): Meta? = getMeta(name, null, null)
public val changes: Flow<Name>
/**
@ -47,9 +52,9 @@ public interface VisionProperties {
public fun invalidate(propertyName: Name)
}
public interface MutableVisionProperties : VisionProperties {
public interface MutableVisionProperties : VisionProperties, MutableMetaProvider {
override fun getProperty(
override fun getMeta(
name: Name,
inherit: Boolean?,
includeStyles: Boolean?,
@ -60,21 +65,31 @@ public interface MutableVisionProperties : VisionProperties {
includeStyles,
)
public fun setProperty(
public fun setMeta(
name: Name,
node: Meta?,
notify: Boolean = true,
notify: Boolean,
)
public fun setValue(
name: Name,
value: Value?,
notify: Boolean = true,
notify: Boolean,
)
override fun getMeta(name: Name): MutableMeta = getMeta(name, null, null)
override fun setMeta(name: Name, node: Meta?) {
setMeta(name, node, true)
}
override fun setValue(name: Name, value: Value?) {
setValue(name, value, true)
}
}
public fun MutableVisionProperties.remove(name: Name) {
setProperty(name, null)
setMeta(name, null)
}
public fun MutableVisionProperties.remove(name: String) {
@ -134,7 +149,7 @@ private class VisionPropertiesItem(
)
override fun setMeta(name: Name, node: Meta?) {
properties.setProperty(nodeName + name, node)
properties.setMeta(nodeName + name, node)
}
override fun toString(): String = Meta.toString(this)
@ -187,7 +202,7 @@ public abstract class AbstractVisionProperties(
return descriptor?.defaultValue
}
override fun setProperty(name: Name, node: Meta?, notify: Boolean) {
override fun setMeta(name: Name, node: Meta?, notify: Boolean) {
//ignore if the value is the same as existing
if (own?.getMeta(name) == node) return
@ -257,11 +272,11 @@ public fun VisionProperties.getValue(
/**
* Get [Vision] property using key as a String
*/
public fun VisionProperties.getProperty(
public fun VisionProperties.getMeta(
name: String,
inherit: Boolean? = null,
includeStyles: Boolean? = null,
): Meta = getProperty(name.parseAsName(), inherit, includeStyles)
): Meta = getMeta(name.parseAsName(), inherit, includeStyles)
/**
* The root property node with given inheritance and style flags
@ -271,33 +286,33 @@ public fun VisionProperties.getProperty(
public fun MutableVisionProperties.root(
inherit: Boolean? = null,
includeStyles: Boolean? = null,
): MutableMeta = getProperty(Name.EMPTY, inherit, includeStyles)
): MutableMeta = getMeta(Name.EMPTY, inherit, includeStyles)
/**
* Get [Vision] property using key as a String
*/
public fun MutableVisionProperties.getProperty(
public fun MutableVisionProperties.getMeta(
name: String,
inherit: Boolean? = null,
includeStyles: Boolean? = null,
): MutableMeta = getProperty(name.parseAsName(), inherit, includeStyles)
): MutableMeta = getMeta(name.parseAsName(), inherit, includeStyles)
public operator fun MutableVisionProperties.set(name: Name, value: Number): Unit =
setValue(name, value.asValue())
public operator fun MutableVisionProperties.set(name: String, value: Number): Unit =
set(name.parseAsName(), value)
public operator fun MutableVisionProperties.set(name: Name, value: Boolean): Unit =
setValue(name, value.asValue())
public operator fun MutableVisionProperties.set(name: String, value: Boolean): Unit =
set(name.parseAsName(), value)
public operator fun MutableVisionProperties.set(name: Name, value: String): Unit =
setValue(name, value.asValue())
public operator fun MutableVisionProperties.set(name: String, value: String): Unit =
set(name.parseAsName(), value)
//
//public operator fun MutableVisionProperties.set(name: Name, value: Number): Unit =
// setValue(name, value.asValue())
//
//public operator fun MutableVisionProperties.set(name: String, value: Number): Unit =
// set(name.parseAsName(), value)
//
//public operator fun MutableVisionProperties.set(name: Name, value: Boolean): Unit =
// setValue(name, value.asValue())
//
//public operator fun MutableVisionProperties.set(name: String, value: Boolean): Unit =
// set(name.parseAsName(), value)
//
//public operator fun MutableVisionProperties.set(name: Name, value: String): Unit =
// setValue(name, value.asValue())
//
//public operator fun MutableVisionProperties.set(name: String, value: String): Unit =
// set(name.parseAsName(), value)

View File

@ -17,10 +17,10 @@ public fun Vision.flowProperty(
includeStyles: Boolean? = null,
): Flow<Meta> = flow {
//Pass initial value.
emit(properties.getProperty(propertyName, inherit, includeStyles))
emit(properties.getMeta(propertyName, inherit, includeStyles))
properties.changes.collect { name ->
if (name.startsWith(propertyName)) {
emit(properties.getProperty(propertyName, inherit, includeStyles))
emit(properties.getMeta(propertyName, inherit, includeStyles))
}
}
}

View File

@ -93,7 +93,6 @@ public fun FlowContent.visionFragment(
updatesUrl: String? = null,
onVisionRendered: (Name, Vision) -> Unit = { _, _ -> },
idPrefix: String? = null,
fragment: HtmlVisionFragment,
): Unit = consumer.visionFragment(
visionManager = visionManager,

View File

@ -6,10 +6,11 @@ import space.kscience.dataforge.meta.boolean
import space.kscience.dataforge.meta.number
import space.kscience.dataforge.meta.string
import space.kscience.dataforge.names.Name
import space.kscience.visionforge.*
import space.kscience.visionforge.AbstractVision
import space.kscience.visionforge.Vision
//TODO replace by something
internal val Vision.mutableProperties get() = properties.getProperty(Name.EMPTY, false, false)
internal val Vision.mutableProperties get() = properties.getMeta(Name.EMPTY, false, false)
@Serializable
public abstract class VisionOfHtmlInput : AbstractVision() {

View File

@ -23,10 +23,10 @@ public fun Vision.useProperty(
callback: (Meta) -> Unit,
): Job {
//Pass initial value.
callback(properties.getProperty(propertyName, inherit, includeStyles))
callback(properties.getMeta(propertyName, inherit, includeStyles))
return properties.changes.onEach { name ->
if (name.startsWith(propertyName)) {
callback(properties.getProperty(propertyName, inherit, includeStyles))
callback(properties.getMeta(propertyName, inherit, includeStyles))
}
}.launchIn(scope ?: error("Orphan Vision can't observe properties"))
}

View File

@ -4,6 +4,7 @@ import kotlinx.html.*
import kotlinx.html.stream.createHTML
import space.kscience.dataforge.context.Global
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.set
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name
import space.kscience.visionforge.*

View File

@ -42,7 +42,7 @@ internal class VisionPropertyTest {
@Test
fun testPropertyEdit() {
val vision = manager.group()
vision.properties.getProperty("fff.ddd").apply {
vision.properties.getMeta("fff.ddd").apply {
value = 2.asValue()
}
assertEquals(2, vision.properties.getValue("fff.ddd")?.int)
@ -52,7 +52,7 @@ internal class VisionPropertyTest {
@Test
fun testPropertyUpdate() {
val vision = manager.group()
vision.properties.getProperty("fff").updateWith(TestScheme) {
vision.properties.getMeta("fff").updateWith(TestScheme) {
ddd = 2
}
assertEquals(2, vision.properties.getValue("fff.ddd")?.int)
@ -87,7 +87,7 @@ internal class VisionPropertyTest {
child.properties.remove("test")
assertEquals(11, child.properties.getProperty("test", inherit = true).int)
assertEquals(11, child.properties.getMeta("test", inherit = true).int)
// assertEquals(11, deferred.await()?.int)
// assertEquals(2, callCounter)
subscription.cancel()

View File

@ -4,6 +4,7 @@ import kotlinx.browser.document
import kotlinx.browser.window
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
@ -11,10 +12,7 @@ import kotlinx.coroutines.sync.withLock
import org.w3c.dom.*
import org.w3c.dom.url.URL
import space.kscience.dataforge.context.*
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaSerializer
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.int
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.parseAsName
import space.kscience.visionforge.html.VisionTagConsumer
@ -68,7 +66,7 @@ public class VisionClient : AbstractPlugin() {
/**
* Communicate vision property changed from rendering engine to model
*/
public fun visionPropertyChanged(visionName: Name, propertyName: Name, item: Meta?) {
public fun notifyPropertyChanged(visionName: Name, propertyName: Name, item: Meta?) {
context.launch {
mutex.withLock {
changeCollector.propertyChanged(visionName, propertyName, item)
@ -76,9 +74,17 @@ public class VisionClient : AbstractPlugin() {
}
}
// public fun visionChanged(name: Name?, child: Vision?) {
// changeCollector.setChild(name, child)
// }
private val eventCollector by lazy {
MutableSharedFlow<VisionEvent>(meta["feedback.eventCache"].int ?: 100)
}
/**
* Send a custom feedback event
*/
public suspend fun sendEvent(event: VisionEvent) {
eventCollector.emit(event)
}
private fun renderVision(element: Element, name: Name, vision: Vision, outputMeta: Meta) {
vision.setAsRoot(visionManager)
@ -103,7 +109,7 @@ public class VisionClient : AbstractPlugin() {
logger.info { "Updating vision data from $wsUrl" }
//Individual websocket for this element
//Individual websocket for this vision
WebSocket(wsUrl.toString()).apply {
onmessage = { messageEvent ->
val stringData: String? = messageEvent.data as? String
@ -131,7 +137,7 @@ public class VisionClient : AbstractPlugin() {
var feedbackJob: Job? = null
//Feedback changes aggregation time in milliseconds
val feedbackAggregationTime = meta["aggregationTime"]?.int ?: 300
val feedbackAggregationTime = meta["feedback.aggregationTime"]?.int ?: 300
onopen = {
feedbackJob = visionManager.context.launch {
@ -144,18 +150,24 @@ public class VisionClient : AbstractPlugin() {
change.reset()
}
}
// // take channel for given vision name
// eventCollector[name]?.let { channel ->
// for (e in channel) {
// send(visionManager.jsonFormat.encodeToString(VisionEvent.serializer(), e))
// }
// }
}
}
logger.info { "WebSocket update channel established for output '$name'" }
logger.info { "WebSocket feedback channel established for output '$name'" }
}
onclose = {
feedbackJob?.cancel()
logger.info { "WebSocket update channel closed for output '$name'" }
logger.info { "WebSocket feedback channel closed for output '$name'" }
}
onerror = {
feedbackJob?.cancel()
logger.error { "WebSocket update channel error for output '$name'" }
logger.error { "WebSocket feedback channel error for output '$name'" }
}
}
}
@ -248,20 +260,26 @@ public class VisionClient : AbstractPlugin() {
}
}
public fun VisionClient.visionPropertyChanged(visionName: Name, propertyName: String, item: Meta?) {
visionPropertyChanged(visionName, propertyName.parseAsName(true), item)
public fun VisionClient.notifyPropertyChanged(visionName: Name, propertyName: String, item: Meta?) {
notifyPropertyChanged(visionName, propertyName.parseAsName(true), item)
}
public fun VisionClient.visionPropertyChanged(visionName: Name, propertyName: String, item: Number) {
visionPropertyChanged(visionName, propertyName.parseAsName(true), Meta(item))
public fun VisionClient.notifyPropertyChanged(visionName: Name, propertyName: String, item: Number) {
notifyPropertyChanged(visionName, propertyName.parseAsName(true), Meta(item))
}
public fun VisionClient.visionPropertyChanged(visionName: Name, propertyName: String, item: String) {
visionPropertyChanged(visionName, propertyName.parseAsName(true), Meta(item))
public fun VisionClient.notifyPropertyChanged(visionName: Name, propertyName: String, item: String) {
notifyPropertyChanged(visionName, propertyName.parseAsName(true), Meta(item))
}
public fun VisionClient.visionPropertyChanged(visionName: Name, propertyName: String, item: Boolean) {
visionPropertyChanged(visionName, propertyName.parseAsName(true), Meta(item))
public fun VisionClient.notifyPropertyChanged(visionName: Name, propertyName: String, item: Boolean) {
notifyPropertyChanged(visionName, propertyName.parseAsName(true), Meta(item))
}
public fun VisionClient.sendEvent(visionName: Name, event: MetaRepr): Unit {
context.launch {
sendEvent(VisionMetaEvent(visionName, event.toMeta()))
}
}
private fun whenDocumentLoaded(block: Document.() -> Unit): Unit {

View File

@ -36,7 +36,7 @@ internal fun textVisionRenderer(
value = it ?: ""
}
onChangeFunction = {
client.visionPropertyChanged(name, VisionOfTextField::text.name, value)
client.notifyPropertyChanged(name, VisionOfTextField::text.name, value)
}
}
}
@ -58,7 +58,7 @@ internal fun numberVisionRenderer(
value = it?.toDouble() ?: 0.0
}
onChangeFunction = {
client.visionPropertyChanged(name, VisionOfNumberField::value.name, value)
client.notifyPropertyChanged(name, VisionOfNumberField::value.name, value)
}
}
}
@ -106,7 +106,7 @@ internal fun formVisionRenderer(
form.onsubmit = { event ->
event.preventDefault()
val formData = FormData(form).toMeta()
client.visionPropertyChanged(name, VisionOfHtmlForm::values.name, formData)
client.notifyPropertyChanged(name, VisionOfHtmlForm::values.name, formData)
console.info("Sent: ${formData.toMap()}")
false
}

View File

@ -10,7 +10,6 @@ import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name
import space.kscience.plotly.Plot
import space.kscience.plotly.Plotly
@ -34,7 +33,7 @@ public class VisionOfPlotly private constructor(
@Transient
override val properties: MutableVisionProperties = object : MutableVisionProperties {
override fun setProperty(name: Name, node: Meta?, notify: Boolean) {
override fun setMeta(name: Name, node: Meta?, notify: Boolean) {
meta.setMeta(name, node)
}
@ -46,7 +45,7 @@ public class VisionOfPlotly private constructor(
override val descriptor: MetaDescriptor? get() = this@VisionOfPlotly.descriptor
override fun getProperty(
override fun getMeta(
name: Name,
inherit: Boolean?,
includeStyles: Boolean?,

View File

@ -23,10 +23,7 @@ import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.ContextAware
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.names.Name
import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionChange
import space.kscience.visionforge.VisionManager
import space.kscience.visionforge.flowChanges
import space.kscience.visionforge.*
import space.kscience.visionforge.html.*
import kotlin.time.Duration.Companion.milliseconds
@ -77,6 +74,11 @@ public class VisionRoute(
*/
public fun Application.serveVisionData(
configuration: VisionRoute,
onEvent: suspend Vision.(VisionEvent) -> Unit = { event ->
if (event is VisionChange) {
update(event)
}
},
resolveVision: (Name) -> Vision?,
) {
require(WebSockets)
@ -96,11 +98,11 @@ public fun Application.serveVisionData(
launch {
for (frame in incoming) {
val data = frame.data.decodeToString()
application.log.debug("Received update for $name: \n$data")
val change = configuration.visionManager.jsonFormat.decodeFromString(
VisionChange.serializer(), data
application.log.debug("Received event for $name: \n$data")
val event = configuration.visionManager.jsonFormat.decodeFromString(
VisionEvent.serializer(), data
)
vision.update(change)
vision.onEvent(event)
}
}

View File

@ -3,7 +3,10 @@ package space.kscience.visionforge.solid
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import space.kscience.dataforge.names.Name
import space.kscience.visionforge.*
import space.kscience.visionforge.MutableVisionContainer
import space.kscience.visionforge.VisionBuilder
import space.kscience.visionforge.setChild
import space.kscience.visionforge.static
public enum class CompositeType {
GROUP, // Dumb sum of meshes
@ -33,7 +36,7 @@ public inline fun MutableVisionContainer<Solid>.composite(
}
val res = Composite(type, children[0], children[1])
res.properties.setProperty(Name.EMPTY, group.properties.own)
res.properties.setMeta(Name.EMPTY, group.properties.own)
setChild(name, res)
return res

View File

@ -9,37 +9,8 @@ import space.kscience.kmath.geometry.component2
import space.kscience.visionforge.MutableVisionContainer
import space.kscience.visionforge.VisionBuilder
import space.kscience.visionforge.setChild
import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.sin
public typealias Shape2D = List<Float32Vector2D>
@Serializable
public class Shape2DBuilder(private val points: ArrayList<Float32Vector2D> = ArrayList()) {
public fun point(x: Number, y: Number) {
points.add(Float32Vector2D(x, y))
}
public fun build(): Shape2D = points
}
public fun Shape2DBuilder.polygon(vertices: Int, radius: Number) {
require(vertices > 2) { "Polygon must have more than 2 vertices" }
val angle = 2 * PI / vertices
for (i in 0 until vertices) {
point(radius.toDouble() * cos(angle * i), radius.toDouble() * sin(angle * i))
}
}
/**
* A layer for extruded shape
*/
@Serializable
public data class Layer(var x: Float, var y: Float, var z: Float, var scale: Float)
/**
* An extruded shape with the same number of points on each layer.
*/
@ -50,6 +21,12 @@ public class Extruded(
public val layers: List<Layer>,
) : SolidBase<Extruded>(), GeometrySolid {
/**
* A layer for extruded shape
*/
@Serializable
public data class Layer(var x: Float, var y: Float, var z: Float, var scale: Float)
init {
require(shape.size > 2) { "Extruded shape requires more than 2 points per layer" }
}
@ -72,6 +49,8 @@ public class Extruded(
var lowerLayer = layers.first()
var upperLayer: List<Float32Vector3D>
geometryBuilder.cap(layers.first().reversed())
for (i in (1 until layers.size)) {
upperLayer = layers[i]
for (j in (0 until shape.size - 1)) {
@ -93,7 +72,7 @@ public class Extruded(
)
lowerLayer = upperLayer
}
geometryBuilder.cap(layers.first().reversed())
geometryBuilder.cap(layers.last())
}
@ -102,16 +81,18 @@ public class Extruded(
public var layers: MutableList<Layer> = ArrayList(),
public val properties: MutableMeta = MutableMeta(),
) {
@VisionBuilder
public fun shape(block: Shape2DBuilder.() -> Unit) {
this.shape = Shape2DBuilder().apply(block).build()
}
@VisionBuilder
public fun layer(z: Number, x: Number = 0.0, y: Number = 0.0, scale: Number = 1.0) {
layers.add(Layer(x.toFloat(), y.toFloat(), z.toFloat(), scale.toFloat()))
}
internal fun build(): Extruded = Extruded(shape, layers).apply {
this.properties.setProperty(Name.EMPTY, this@Builder.properties)
this.properties.setMeta(Name.EMPTY, this@Builder.properties)
}
}

View File

@ -13,7 +13,10 @@ import space.kscience.visionforge.setChild
public class PolyLine(public val points: List<Float32Vector3D>) : SolidBase<PolyLine>() {
//var lineType by string()
public var thickness: Number by properties.root(inherit = false, includeStyles = true).number { DEFAULT_THICKNESS }
public var thickness: Number by properties.root(
inherit = false,
includeStyles = true
).number { DEFAULT_THICKNESS }
public companion object {
public const val DEFAULT_THICKNESS: Double = 1.0

View File

@ -0,0 +1,26 @@
package space.kscience.visionforge.solid
import kotlinx.serialization.Serializable
import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.sin
public typealias Shape2D = List<Float32Vector2D>
@Serializable
public class Shape2DBuilder(private val points: ArrayList<Float32Vector2D> = ArrayList()) {
public fun point(x: Number, y: Number) {
points.add(Float32Vector2D(x, y))
}
public fun build(): Shape2D = points
}
public fun Shape2DBuilder.polygon(vertices: Int, radius: Number) {
require(vertices > 2) { "Polygon must have more than 2 vertices" }
val angle = 2 * PI / vertices
for (i in 0 until vertices) {
point(radius.toDouble() * cos(angle * i), radius.toDouble() * sin(angle * i))
}
}

View File

@ -11,8 +11,10 @@ import space.kscience.dataforge.names.plus
import space.kscience.kmath.complex.Quaternion
import space.kscience.kmath.complex.QuaternionField
import space.kscience.kmath.geometry.*
import space.kscience.visionforge.*
import space.kscience.visionforge.Vision
import space.kscience.visionforge.Vision.Companion.VISIBLE_KEY
import space.kscience.visionforge.hide
import space.kscience.visionforge.inherited
import space.kscience.visionforge.solid.Solid.Companion.DETAIL_KEY
import space.kscience.visionforge.solid.Solid.Companion.IGNORE_KEY
import space.kscience.visionforge.solid.Solid.Companion.LAYER_KEY
@ -182,7 +184,7 @@ internal fun point(
override fun setValue(thisRef: Solid, property: KProperty<*>, value: Float32Vector3D?) {
if (value == null) {
thisRef.properties.setProperty(name, null)
thisRef.properties.setMeta(name, null)
} else {
thisRef.properties[name + X_KEY] = value.x
thisRef.properties[name + Y_KEY] = value.y

View File

@ -107,12 +107,12 @@ public val Solid.color: ColorAccessor
get() = ColorAccessor(properties.root(true), MATERIAL_COLOR_KEY)
public var Solid.material: SolidMaterial?
get() = SolidMaterial.read(properties.getProperty(MATERIAL_KEY))
set(value) = properties.setProperty(MATERIAL_KEY, value?.meta)
get() = SolidMaterial.read(properties.getMeta(MATERIAL_KEY))
set(value) = properties.setMeta(MATERIAL_KEY, value?.meta)
@VisionBuilder
public fun Solid.material(builder: SolidMaterial.() -> Unit) {
properties.getProperty(MATERIAL_KEY).updateWith(SolidMaterial, builder)
properties.getMeta(MATERIAL_KEY).updateWith(SolidMaterial, builder)
}
public var Solid.opacity: Number?
@ -125,5 +125,5 @@ public var Solid.opacity: Number?
@VisionBuilder
public fun Solid.edges(enabled: Boolean = true, block: SolidMaterial.() -> Unit = {}) {
properties[SolidMaterial.EDGES_ENABLED_KEY] = enabled
SolidMaterial.write(properties.getProperty(SolidMaterial.EDGES_MATERIAL_KEY)).apply(block)
SolidMaterial.write(properties.getMeta(SolidMaterial.EDGES_MATERIAL_KEY)).apply(block)
}

View File

@ -14,7 +14,6 @@ import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.get
import space.kscience.dataforge.names.*
import space.kscience.visionforge.*
import space.kscience.visionforge.AbstractVisionGroup.Companion.updateProperties
import space.kscience.visionforge.solid.SolidReference.Companion.REFERENCE_CHILD_PROPERTY_PREFIX
@ -162,7 +161,7 @@ internal class SolidReferenceChild(
override val properties: MutableVisionProperties = object : MutableVisionProperties {
override val descriptor: MetaDescriptor get() = this@SolidReferenceChild.descriptor
override val own: MutableMeta by lazy { owner.properties.getProperty(childToken(childName).asName()) }
override val own: MutableMeta by lazy { owner.properties.getMeta(childToken(childName).asName()) }
override fun getValue(
name: Name,
@ -170,7 +169,7 @@ internal class SolidReferenceChild(
includeStyles: Boolean?,
): Value? = own.getValue(name) ?: prototype.properties.getValue(name, inherit, includeStyles)
override fun setProperty(name: Name, node: Meta?, notify: Boolean) {
override fun setMeta(name: Name, node: Meta?, notify: Boolean) {
own.setMeta(name, node)
}
@ -185,20 +184,6 @@ internal class SolidReferenceChild(
}
}
override fun update(change: VisionChange) {
change.children?.forEach { (name, change) ->
when {
change.vision == NullVision -> error("Deleting children inside ref is not allowed.")
change.vision != null -> error("Updating content of the ref is not allowed")
else -> children.getChild(name)?.update(change)
}
}
change.properties?.let {
updateProperties(it, Name.EMPTY)
}
}
override val children: VisionChildren = object : VisionChildren {
override val parent: Vision get() = this@SolidReferenceChild

View File

@ -41,6 +41,7 @@ public class Solids(meta: Meta) : VisionPlugin(meta), MutableVisionContainer<Sol
subclass(ConeSurface.serializer())
subclass(Convex.serializer())
subclass(Extruded.serializer())
subclass(Surface.serializer())
subclass(PolyLine.serializer())
subclass(SolidLabel.serializer())
subclass(Sphere.serializer())

View File

@ -2,9 +2,14 @@ package space.kscience.visionforge.solid
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import space.kscience.dataforge.meta.MutableMeta
import space.kscience.dataforge.names.Name
import space.kscience.kmath.geometry.component1
import space.kscience.kmath.geometry.component2
import space.kscience.kmath.structures.Float32
import space.kscience.visionforge.MutableVisionContainer
import space.kscience.visionforge.VisionBuilder
import space.kscience.visionforge.setChild
private inline fun <T> Iterable<T>.sumOf(selector: (T) -> Float32): Float32 {
@ -20,9 +25,9 @@ private inline fun <T> Iterable<T>.sumOf(selector: (T) -> Float32): Float32 {
*/
@Serializable
@SerialName("solid.surface")
public class LayersSurface(
public class Surface(
public val layers: List<Layer>,
) : SolidBase<Extruded>(), GeometrySolid {
) : SolidBase<Surface>(), GeometrySolid {
@Serializable
public data class Layer(val z: Float32, val outer: Shape2D, val inner: Shape2D?) {
@ -58,7 +63,7 @@ public class LayersSurface(
//outer and inner
for (i in 0 until layers.size - 1) {
val bottom = layers[i]
val top = layers[i+1]
val top = layers[i + 1]
//creating shape in x-y plane with z = 0
val bottomOuterPoints = bottom.outerPoints()
@ -130,7 +135,39 @@ public class LayersSurface(
}
}
public class Builder(
public var layers: MutableList<Layer> = ArrayList(),
public val properties: MutableMeta = MutableMeta(),
) {
public fun layer(
z: Number,
innerBuilder: (Shape2DBuilder.() -> Unit)? = null,
outerBuilder: Shape2DBuilder.() -> Unit,
) {
layers.add(
Layer(
z.toFloat(),
outer = Shape2DBuilder().apply(outerBuilder).build(),
inner = innerBuilder?.let { Shape2DBuilder().apply(innerBuilder).build() }
)
)
}
internal fun build(): Surface = Surface(layers).apply {
properties.setMeta(Name.EMPTY, this@Builder.properties)
}
}
public companion object {
public const val TYPE: String = "solid.surface"
}
}
@VisionBuilder
public fun MutableVisionContainer<Solid>.surface(
name: String? = null,
action: Surface.Builder.() -> Unit = {},
): Surface = Surface.Builder().apply(action).build().also { setChild(name, it) }

View File

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

View File

@ -5,6 +5,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.test.runTest
import space.kscience.dataforge.meta.int
import space.kscience.dataforge.meta.set
import space.kscience.dataforge.meta.string
import space.kscience.dataforge.names.asName
import space.kscience.visionforge.*

View File

@ -24,7 +24,7 @@ public object ThreeLineFactory : ThreeFactory<PolyLine> {
}
val material = ThreeMaterials.getLineMaterial(
vision.properties.getProperty(SolidMaterial.MATERIAL_KEY),
vision.properties.getMeta(SolidMaterial.MATERIAL_KEY),
false
)

View File

@ -83,7 +83,7 @@ public object ThreeMaterials {
private val visionMaterialCache = HashMap<Vision, Material>()
internal fun cacheMaterial(vision: Vision): Material = visionMaterialCache.getOrPut(vision) {
buildMaterial(vision.properties.getProperty(SolidMaterial.MATERIAL_KEY)).apply {
buildMaterial(vision.properties.getMeta(SolidMaterial.MATERIAL_KEY)).apply {
cached = true
}
}
@ -133,11 +133,11 @@ public fun Mesh.setMaterial(vision: Vision) {
} else {
material = vision.parent?.let { parent ->
//TODO cache parent material
ThreeMaterials.buildMaterial(parent.properties.getProperty(SolidMaterial.MATERIAL_KEY))
ThreeMaterials.buildMaterial(parent.properties.getMeta(SolidMaterial.MATERIAL_KEY))
} ?: ThreeMaterials.cacheMaterial(vision)
}
} else {
material = ThreeMaterials.buildMaterial(vision.properties.getProperty(SolidMaterial.MATERIAL_KEY))
material = ThreeMaterials.buildMaterial(vision.properties.getMeta(SolidMaterial.MATERIAL_KEY))
}
}
@ -153,18 +153,18 @@ public fun Mesh.updateMaterialProperty(vision: Vision, propertyName: Name) {
when (propertyName) {
SolidMaterial.MATERIAL_COLOR_KEY -> {
material.asDynamic().color =
vision.properties.getProperty(SolidMaterial.MATERIAL_COLOR_KEY).threeColor()
vision.properties.getMeta(SolidMaterial.MATERIAL_COLOR_KEY).threeColor()
?: ThreeMaterials.DEFAULT_COLOR
}
SolidMaterial.SPECULAR_COLOR_KEY -> {
material.asDynamic().specular =
vision.properties.getProperty(SolidMaterial.SPECULAR_COLOR_KEY).threeColor()
vision.properties.getMeta(SolidMaterial.SPECULAR_COLOR_KEY).threeColor()
?: ThreeMaterials.DEFAULT_COLOR
}
SolidMaterial.MATERIAL_EMISSIVE_COLOR_KEY -> {
material.asDynamic().emissive = vision.properties.getProperty(SolidMaterial.MATERIAL_EMISSIVE_COLOR_KEY)
material.asDynamic().emissive = vision.properties.getMeta(SolidMaterial.MATERIAL_EMISSIVE_COLOR_KEY)
.threeColor()
?: ThreeMaterials.BLACK_COLOR
}

View File

@ -76,7 +76,7 @@ public fun Mesh.applyEdges(vision: Solid) {
val edges = children.find { it.name == EDGES_OBJECT_NAME } as? LineSegments
//inherited edges definition, enabled by default
if (vision.properties.getValue(EDGES_ENABLED_KEY, inherit = false)?.boolean != false) {
val material = ThreeMaterials.getLineMaterial(vision.properties.getProperty(EDGES_MATERIAL_KEY), true)
val material = ThreeMaterials.getLineMaterial(vision.properties.getMeta(EDGES_MATERIAL_KEY), true)
if (edges == null) {
add(
LineSegments(