[WIP] simplification of API

This commit is contained in:
2025-01-29 14:57:28 +03:00
parent 78b14a3641
commit 6086dd684d
94 changed files with 802 additions and 784 deletions

View File

@@ -5,12 +5,16 @@
### Added
### Changed
- Simplified Vision and VisionGroup logic. Observation logic moved out.
- Use `Name` for child designation and `Path` for tree access
### Deprecated
### Removed
- VisionChildren and VisionProperties
### Fixed
- Vision server now automatically switches to WSS protocol for updates if incoming protocol is HTTPS
### Security

View File

@@ -6,7 +6,7 @@ plugins {
alias(spclibs.plugins.kotlinx.kover)
}
val dataforgeVersion by extra("0.9.0")
val dataforgeVersion by extra("0.10.0")
allprojects {
group = "space.kscience"
@@ -26,7 +26,7 @@ subprojects {
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
compilerOptions {
freeCompilerArgs.add("-Xcontext-receivers")
freeCompilerArgs.addAll("-Xcontext-receivers")
}
}
@@ -44,7 +44,7 @@ ksciencePublish {
useSPCTeam()
}
repository("spc","https://maven.sciprog.center/kscience")
sonatype()
central()
}
apiValidation {

View File

@@ -4,7 +4,7 @@ import kotlinx.serialization.Serializable
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray
import space.kscience.visionforge.solid.Float32Vector3D
import space.kscience.kmath.geometry.euclidean3d.Float32Vector3D
@Serializable

View File

@@ -1,9 +1,13 @@
package ru.mipt.npm.root
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.names.*
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.plus
import space.kscience.dataforge.names.withIndex
import space.kscience.kmath.complex.Quaternion
import space.kscience.kmath.geometry.fromRotationMatrix
import space.kscience.kmath.geometry.euclidean3d.Float32Vector3D
import space.kscience.kmath.geometry.euclidean3d.fromRotationMatrix
import space.kscience.kmath.linear.VirtualMatrix
import space.kscience.visionforge.MutableVisionContainer
import space.kscience.visionforge.solid.*
@@ -194,7 +198,7 @@ private fun SolidGroup.addShape(
val startphi = degToRad(fPhi1)
val deltaphi = degToRad(fDphi)
fun Shape2DBuilder.pGon(radius: Double){
fun Shape2DBuilder.pGon(radius: Double) {
(0..<fNedges).forEach {
val phi = deltaphi / fNedges * it + startphi
point(radius * cos(phi), radius * sin(phi))
@@ -204,7 +208,7 @@ private fun SolidGroup.addShape(
surface(name) {
//getting the radius of first
require(fNz > 1) { "The polyhedron geometry requires at least two planes" }
for (index in 0 until fNz){
for (index in 0 until fNz) {
layer(
fZ[index],
innerBuilder = {
@@ -261,7 +265,7 @@ private fun SolidGroup.addShape(
val fShape by shape.dObject(::DGeoShape)
val fScale by shape.dObject(::DGeoScale)
fShape?.let { scaledShape ->
solidGroup(name?.let { Name.parse(it) }) {
solidGroup(name?.let { NameToken.parse(it) }) {
scale = Float32Vector3D(fScale?.x ?: 1.0, fScale?.y ?: 1.0, fScale?.z ?: 1.0)
addShape(scaledShape, context)
apply(block)
@@ -356,10 +360,10 @@ private fun buildVolume(volume: DGeoVolume, context: RootToSolidContext): Solid?
}
}
}
return if (group.items.isEmpty()) {
return if (group.visions.isEmpty()) {
null
} else if (group.items.size == 1 && group.properties.isEmpty()) {
group.items.values.first().apply { parent = null }
} else if (group.visions.size == 1 && group.properties.isEmpty()) {
group.visions.values.first().apply { parent = null }
} else {
group
}.apply {
@@ -384,25 +388,26 @@ private fun SolidGroup.addRootVolume(
cache: Boolean = true,
block: Solid.() -> Unit = {},
) {
val combinedName = name?.parseAsName()?.let {
val combinedName: NameToken = name?.let {
val token = NameToken.parse(it)
// this fix is required to work around malformed root files with duplicated node names
if (get(it) != null) {
it.withIndex(volume.hashCode().toString(16))
token.withIndex(volume.hashCode().toString(16))
} else {
it
token
}
} ?: NameToken("volume[${volume.hashCode().toString(16)}]").asName()
} ?: NameToken("volume[${volume.hashCode().toString(16)}]")
if (!cache) {
val group = buildVolume(volume, context)?.apply(block) ?: return
setVision(combinedName, group)
set(combinedName, group)
} else {
val templateName = volumesName + volume.name
val existing = context.prototypeHolder.getPrototype(templateName)
if (existing == null) {
context.prototypeHolder.prototypes {
val group = buildVolume(volume, context) ?: return@prototypes
setVision(templateName, group)
set(templateName, group)
}
}
@@ -415,7 +420,7 @@ public fun MutableVisionContainer<Solid>.rootGeo(
name: String? = null,
maxLayer: Int = 5,
ignoreRootColors: Boolean = false,
): SolidGroup = solidGroup(name = name?.parseAsName()) {
): SolidGroup = solidGroup(token = name?.let { NameToken.parse(it) }) {
val context = RootToSolidContext(this, maxLayer = maxLayer, ignoreRootColors = ignoreRootColors)
dGeoManager.fNodes.forEach { node ->
addRootNode(node, context)

View File

@@ -3,6 +3,7 @@ package ru.mipt.npm.root.serialization
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.plus
import space.kscience.kmath.geometry.euclidean3d.Float32Vector3D
import space.kscience.visionforge.MutableVisionContainer
import space.kscience.visionforge.solid.*
import kotlin.math.PI

View File

@@ -1,7 +1,7 @@
package ru.mipt.npm.muon.monitor
import kotlinx.serialization.Serializable
import space.kscience.visionforge.solid.Float32Vector3D
import space.kscience.kmath.geometry.euclidean3d.Float32Vector3D
typealias Track = List<Float32Vector3D>

View File

@@ -3,12 +3,11 @@ package ru.mipt.npm.muon.monitor
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.UPPER_LAYER_Z
import space.kscience.dataforge.names.asName
import space.kscience.kmath.geometry.euclidean3d.Float32Vector3D
import space.kscience.visionforge.MutableVisionContainer
import space.kscience.visionforge.VisionManager
import space.kscience.visionforge.setAsRoot
import space.kscience.visionforge.solid.*
import kotlin.collections.set
import kotlin.math.PI
class Model(val manager: VisionManager) {
@@ -61,7 +60,7 @@ class Model(val manager: VisionManager) {
}
}
setVision("tracks".asName(), tracks)
setVision("tracks", tracks)
}
private fun highlight(pixel: String) {
@@ -73,7 +72,7 @@ class Model(val manager: VisionManager) {
map.values.forEach {
it.properties[SolidMaterial.MATERIAL_COLOR_KEY] = null
}
tracks.items.keys.forEach {
tracks.visions.keys.forEach {
tracks.setVision(it, null)
}
}

View File

@@ -2,8 +2,8 @@ package ru.mipt.npm.muon.monitor
import ru.mipt.npm.muon.monitor.Monitor.PIXEL_XY_SIZE
import ru.mipt.npm.muon.monitor.Monitor.PIXEL_Z_SIZE
import space.kscience.visionforge.solid.Float32Euclidean3DSpace
import space.kscience.visionforge.solid.Float32Vector3D
import space.kscience.kmath.geometry.euclidean3d.Float32Space3D
import space.kscience.kmath.geometry.euclidean3d.Float32Vector3D
/**
* A single pixel
@@ -111,7 +111,7 @@ class SC16(
}
val offset = Float32Vector3D(-y, x, 0)//rotateDetector(Point3D(x, y, 0.0));
val pixelName = "${name}_${index}"
SC1(pixelName, with(Float32Euclidean3DSpace) { offset + center })
SC1(pixelName, with(Float32Space3D) { offset + center })
}
}
}

View File

@@ -1,11 +1,9 @@
package ru.mipt.npm.muon.monitor
actual fun readResource(path: String): String {
return kotlinext.js.require(path) as String
}
actual fun readResource(path: String): String = js("require(path)")
// TODO replace by resource
internal actual fun readMonitorConfig(): String{
internal actual fun readMonitorConfig(): String {
return """
--Place-|-SC16-|-TB-CHN-|-HB-CHN-|-X-coord-|-Y-coord-|-Z-coord-|-Theta-|-Phi
----------------------------------------------------------------------------

View File

@@ -5,7 +5,6 @@ import io.ktor.http.ContentType
import io.ktor.http.HttpStatusCode
import io.ktor.serialization.kotlinx.json.json
import io.ktor.server.application.Application
import io.ktor.server.application.call
import io.ktor.server.application.install
import io.ktor.server.application.log
import io.ktor.server.cio.CIO
@@ -14,8 +13,8 @@ import io.ktor.server.http.content.staticResources
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
import io.ktor.server.response.respond
import io.ktor.server.response.respondText
import io.ktor.server.routing.Routing
import io.ktor.server.routing.get
import io.ktor.server.routing.routing
import org.apache.commons.math3.random.JDKRandomGenerator
import ru.mipt.npm.muon.monitor.sim.Cos2TrackGenerator
import ru.mipt.npm.muon.monitor.sim.simulateOne
@@ -39,7 +38,8 @@ fun Application.module(context: Context = Global) {
install(ContentNegotiation) {
json()
}
install(Routing) {
routing {
get("/event") {
val event = generator.simulateOne()
call.respond(event)
@@ -53,6 +53,7 @@ fun Application.module(context: Context = Global) {
}
staticResources("/", null)
}
try {
Desktop.getDesktop().browse(URI("http://localhost:8080/index.html"))
} catch (ex: Exception) {

View File

@@ -1,10 +1,7 @@
package ru.mipt.npm.muon.monitor
actual fun readResource(path: String): String {
return ClassLoader.getSystemClassLoader().getResourceAsStream(path)?.readBytes()?.decodeToString()
actual fun readResource(path: String): String =
ClassLoader.getSystemClassLoader().getResourceAsStream(path)?.readBytes()?.decodeToString()
?: error("Resource '$path' not found")
}
internal actual fun readMonitorConfig(): String {
return readResource("map-RMM110.sc16")
}
internal actual fun readMonitorConfig(): String = readResource("map-RMM110.sc16")

View File

@@ -5,7 +5,7 @@ import org.apache.commons.math3.geometry.euclidean.threed.Plane
import org.apache.commons.math3.geometry.euclidean.threed.Vector3D
import ru.mipt.npm.muon.monitor.Monitor.CENTRAL_LAYER_Z
import ru.mipt.npm.muon.monitor.Monitor.GEOMETRY_TOLERANCE
import space.kscience.visionforge.solid.Float32Vector3D
import space.kscience.kmath.geometry.euclidean3d.Float32Vector3D
/**
* Created by darksnake on 11-May-16.

View File

@@ -11,12 +11,11 @@ import kotlinx.html.a
import kotlinx.html.h1
import space.kscience.dataforge.context.Global
import space.kscience.dataforge.context.request
import space.kscience.plotly.PlotlyPlugin
import space.kscience.plotly.layout
import space.kscience.plotly.models.Trace
import space.kscience.plotly.models.invoke
import space.kscience.plotly.plotly
import space.kscience.visionforge.html.VisionPage
import space.kscience.visionforge.plotly.PlotlyPlugin
import space.kscience.visionforge.plotly.plotly
import space.kscience.visionforge.server.close
import space.kscience.visionforge.server.openInBrowser
import space.kscience.visionforge.server.visionPage
@@ -53,8 +52,6 @@ fun main() {
h1 { +"This is the plot page" }
a("/other") { +"The other page" }
vision {
plotly {
traces(sinTrace, cosTrace)
layout {

View File

@@ -17,7 +17,7 @@ import space.kscience.visionforge.server.openInBrowser
import space.kscience.visionforge.server.visionPage
@Suppress("ExtractKtorModule")
fun main() {
suspend fun main() {
val visionManager = Global.request(VisionManager)
val server = embeddedServer(CIO) {

View File

@@ -6,9 +6,9 @@ import io.ktor.server.http.content.staticResources
import io.ktor.server.routing.routing
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.Global
import space.kscience.plotly.PlotlyPlugin
import space.kscience.visionforge.html.*
import space.kscience.visionforge.markup.MarkupPlugin
import space.kscience.visionforge.plotly.PlotlyPlugin
import space.kscience.visionforge.server.close
import space.kscience.visionforge.server.openInBrowser
import space.kscience.visionforge.server.visionPage
@@ -19,7 +19,7 @@ import java.awt.Desktop
import java.nio.file.Path
public fun makeVisionFile(
public suspend fun makeVisionFile(
path: Path? = null,
title: String = "VisionForge page",
resourceLocation: ResourceLocation = ResourceLocation.SYSTEM,
@@ -39,7 +39,7 @@ public fun makeVisionFile(
if (show) Desktop.getDesktop().browse(actualPath.toFile().toURI())
}
public fun serve(
public suspend fun serve(
title: String = "VisionForge page",
show: Boolean = true,
content: HtmlVisionFragment,

View File

@@ -25,7 +25,7 @@ import kotlin.random.Random
@Suppress("ExtractKtorModule")
fun main() {
suspend fun main() {
val satContext = Context("sat") {
plugin(Solids)
}

View File

@@ -4,7 +4,8 @@ import kotlinx.coroutines.*
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.invoke
import space.kscience.dataforge.names.Name
import space.kscience.kmath.geometry.Euclidean3DSpace
import space.kscience.kmath.geometry.euclidean3d.Float32Vector3D
import space.kscience.kmath.geometry.euclidean3d.Float64Space3D
import space.kscience.kmath.geometry.radians
import space.kscience.visionforge.Colors
import space.kscience.visionforge.solid.*
@@ -110,11 +111,11 @@ fun VisionLayout<Solid>.showcase() {
rotationY = PI / 4
axes(200)
box(100, 100, 100) {
rotate((PI / 4).radians, Euclidean3DSpace.zAxis)
rotate((PI / 4).radians, Float64Space3D.zAxis)
GlobalScope.launch(Dispatchers.Main) {
while (isActive) {
delay(100)
rotate((PI / 20).radians, Euclidean3DSpace.yAxis)
rotate((PI / 20).radians, Float64Space3D.yAxis)
}
}
color(Colors.red)

View File

@@ -4,4 +4,7 @@ kotlin.mpp.stability.nowarn=true
org.gradle.parallel=true
org.gradle.jvmargs=-Xmx4G
toolsVersion=0.15.4-kotlin-2.0.0
org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
kotlin.native.enableKlibsCrossCompilation=true
toolsVersion=0.16.1-kotlin-2.1.0

View File

@@ -5,7 +5,7 @@ plugins{
allprojects {
group = "space.kscience"
version = "0.7.2"
version = "0.8.0"
}
readme {

View File

@@ -2,24 +2,54 @@
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"metadata": {
"ExecuteTime": {
"end_time": "2025-01-29T05:44:54.673735900Z",
"start_time": "2025-01-29T05:44:53.204850Z"
}
},
"source": [
"import space.kscience.plotly.PlotlyIntegration\n",
"USE(PlotlyIntegration())\n",
"//@file:CompilerArgs(\"-jvm-target=11\")\n",
"//@file:Repository(\"https://repo.kotlin.link\")\n",
"//@file:DependsOn(\"space.kscience:plotlykt-jupyter-jvm:0.7.1\")"
]
"//import space.kscience.plotly.PlotlyIntegration\n",
"//USE(PlotlyIntegration())\n",
"@file:CompilerArgs(\"-jvm-target=11\")\n",
"@file:Repository(\"https://repo.kotlin.link\")\n",
"@file:DependsOn(\"space.kscience:plotlykt-jupyter-jvm:0.7.1.1\")"
],
"outputs": [],
"execution_count": 9
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2025-01-29T05:44:45.885663700Z",
"start_time": "2025-01-29T05:44:45.777526100Z"
}
},
"cell_type": "code",
"source": "Plotly.jupyter.notebook()",
"outputs": [
{
"data": {
"text/html": [
"<div style=\"color: blue;\">Plotly notebook integration switched into the notebook mode.</div>\n"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"execution_count": 2
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": []
"tags": [],
"ExecuteTime": {
"end_time": "2025-01-29T05:44:56.131336600Z",
"start_time": "2025-01-29T05:44:55.855736400Z"
}
},
"outputs": [],
"source": [
"import kotlin.math.*\n",
"\n",
@@ -47,11 +77,49 @@
" }\n",
"\n",
"}"
]
],
"outputs": [
{
"data": {
"text/html": [
"<html>\n",
" <head>\n",
" <meta charset=\"utf-8\">\n",
" <title>Plotly.kt</title>\n",
" <script type=\"text/javascript\" src=\"https://cdn.plot.ly/plotly-2.24.1.min.js\"></script>\n",
" </head>\n",
" <body>\n",
" <h1>A custom header</h1>\n",
" <hr>\n",
" <div id=\"below\">\n",
" <script>if(typeof Plotly !== \"undefined\"){\n",
" Plotly.react(\n",
" 'below',\n",
" [{\"y\":[0.0,0.06279051952931337,0.12533323356430426,0.1873813145857246,0.2486898871648548,0.3090169943749474,0.3681245526846779,0.4257792915650727,0.4817536741017153,0.5358267949789967,0.5877852522924731,0.6374239897486896,0.6845471059286886,0.7289686274214116,0.7705132427757893,0.8090169943749475,0.8443279255020151,0.8763066800438637,0.9048270524660196,0.9297764858882513,0.9510565162951535,0.9685831611286311,0.9822872507286886,0.9921147013144779,0.9980267284282716,1.0,0.9980267284282716,0.9921147013144778,0.9822872507286886,0.9685831611286312,0.9510565162951536,0.9297764858882513,0.9048270524660195,0.8763066800438635,0.844327925502015,0.8090169943749475,0.7705132427757893,0.7289686274214114,0.6845471059286888,0.6374239897486899,0.5877852522924732,0.535826794978997,0.4817536741017156,0.4257792915650729,0.36812455268467814,0.3090169943749475,0.24868988716485482,0.18738131458572502,0.12533323356430454,0.06279051952931358,1.2246467991473532E-16,-0.06279051952931335,-0.12533323356430429,-0.18738131458572477,-0.24868988716485502,-0.30901699437494773,-0.3681245526846783,-0.42577929156507227,-0.481753674101715,-0.5358267949789964,-0.587785252292473,-0.6374239897486896,-0.6845471059286887,-0.7289686274214113,-0.7705132427757894,-0.8090169943749473,-0.8443279255020153,-0.8763066800438636,-0.9048270524660198,-0.9297764858882511,-0.9510565162951535,-0.968583161128631,-0.9822872507286887,-0.9921147013144778,-0.9980267284282716,-1.0,-0.9980267284282716,-0.9921147013144779,-0.9822872507286887,-0.9685831611286311,-0.9510565162951536,-0.9297764858882512,-0.9048270524660199,-0.8763066800438638,-0.8443279255020155,-0.8090169943749476,-0.7705132427757896,-0.7289686274214116,-0.684547105928689,-0.6374239897486896,-0.5877852522924734,-0.5358267949789963,-0.4817536741017153,-0.4257792915650722,-0.3681245526846787,-0.3090169943749476,-0.24868988716485535,-0.18738131458572468,-0.12533323356430465,-0.06279051952931326,-2.4492935982947064E-16],\"x\":[0.0,0.01,0.02,0.03,0.04,0.05,0.06,0.07,0.08,0.09,0.1,0.11,0.12,0.13,0.14,0.15,0.16,0.17,0.18,0.19,0.2,0.21,0.22,0.23,0.24,0.25,0.26,0.27,0.28,0.29,0.3,0.31,0.32,0.33,0.34,0.35,0.36,0.37,0.38,0.39,0.4,0.41,0.42,0.43,0.44,0.45,0.46,0.47,0.48,0.49,0.5,0.51,0.52,0.53,0.54,0.55,0.56,0.57,0.58,0.59,0.6,0.61,0.62,0.63,0.64,0.65,0.66,0.67,0.68,0.69,0.7,0.71,0.72,0.73,0.74,0.75,0.76,0.77,0.78,0.79,0.8,0.81,0.82,0.83,0.84,0.85,0.86,0.87,0.88,0.89,0.9,0.91,0.92,0.93,0.94,0.95,0.96,0.97,0.98,0.99,1.0],\"name\":\"sin\"},{\"y\":[1.0,0.9980267284282716,0.9921147013144779,0.9822872507286887,0.9685831611286311,0.9510565162951535,0.9297764858882515,0.9048270524660195,0.8763066800438636,0.8443279255020151,0.8090169943749475,0.7705132427757893,0.7289686274214116,0.6845471059286886,0.6374239897486896,0.5877852522924731,0.5358267949789965,0.48175367410171516,0.42577929156507266,0.3681245526846781,0.30901699437494745,0.24868988716485496,0.18738131458572474,0.12533323356430426,0.06279051952931353,6.123233995736766E-17,-0.0627905195293134,-0.12533323356430437,-0.18738131458572482,-0.24868988716485463,-0.30901699437494734,-0.368124552684678,-0.4257792915650727,-0.48175367410171543,-0.5358267949789969,-0.587785252292473,-0.6374239897486897,-0.6845471059286887,-0.7289686274214113,-0.7705132427757891,-0.8090169943749473,-0.8443279255020149,-0.8763066800438634,-0.9048270524660194,-0.9297764858882513,-0.9510565162951535,-0.9685831611286311,-0.9822872507286886,-0.9921147013144778,-0.9980267284282716,-1.0,-0.9980267284282716,-0.9921147013144779,-0.9822872507286886,-0.9685831611286311,-0.9510565162951535,-0.9297764858882512,-0.9048270524660197,-0.8763066800438637,-0.8443279255020152,-0.8090169943749476,-0.7705132427757893,-0.7289686274214116,-0.684547105928689,-0.6374239897486895,-0.5877852522924732,-0.5358267949789963,-0.48175367410171527,-0.42577929156507216,-0.3681245526846786,-0.30901699437494756,-0.2486898871648553,-0.18738131458572463,-0.1253332335643046,-0.06279051952931321,-1.8369701987210297E-16,0.06279051952931283,0.12533323356430423,0.18738131458572427,0.24868988716485493,0.30901699437494723,0.36812455268467825,0.4257792915650718,0.48175367410171493,0.535826794978996,0.5877852522924729,0.6374239897486893,0.6845471059286886,0.7289686274214112,0.7705132427757894,0.8090169943749473,0.8443279255020153,0.8763066800438636,0.9048270524660197,0.9297764858882511,0.9510565162951535,0.968583161128631,0.9822872507286887,0.9921147013144778,0.9980267284282716,1.0],\"x\":[0.0,0.01,0.02,0.03,0.04,0.05,0.06,0.07,0.08,0.09,0.1,0.11,0.12,0.13,0.14,0.15,0.16,0.17,0.18,0.19,0.2,0.21,0.22,0.23,0.24,0.25,0.26,0.27,0.28,0.29,0.3,0.31,0.32,0.33,0.34,0.35,0.36,0.37,0.38,0.39,0.4,0.41,0.42,0.43,0.44,0.45,0.46,0.47,0.48,0.49,0.5,0.51,0.52,0.53,0.54,0.55,0.56,0.57,0.58,0.59,0.6,0.61,0.62,0.63,0.64,0.65,0.66,0.67,0.68,0.69,0.7,0.71,0.72,0.73,0.74,0.75,0.76,0.77,0.78,0.79,0.8,0.81,0.82,0.83,0.84,0.85,0.86,0.87,0.88,0.89,0.9,0.91,0.92,0.93,0.94,0.95,0.96,0.97,0.98,0.99,1.0],\"name\":\"cos\"}],\n",
" {\"title\":{\"text\":\"The plot below\"},\"xaxis\":{\"title\":\"x axis name\"},\"yaxis\":{\"title\":\"y axis name\"}},\n",
" {\"toImageButtonOptions\":{\"format\":\"svg\"},\"responsive\":true}\n",
" ); \n",
"} else {\n",
" console.error(\"Plotly not loaded\")\n",
"}</script>\n",
" </div>\n",
" </body>\n",
"</html>\n"
]
},
"execution_count": 10,
"metadata": {
"text/html": {
"isolated": true
}
},
"output_type": "execute_result"
}
],
"execution_count": 10
},
{
"cell_type": "code",
"outputs": [],
"source": [
"HTML(\n",
" \"\"\"\n",
@@ -62,13 +130,30 @@
")"
],
"metadata": {
"collapsed": false
"collapsed": false,
"ExecuteTime": {
"end_time": "2025-01-29T05:44:47.639174900Z",
"start_time": "2025-01-29T05:44:47.443119300Z"
}
},
"execution_count": null
"outputs": [
{
"data": {
"text/html": [
"<div id=\"debug\">\n",
" debug\n",
"</div>"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"execution_count": 4
},
{
"cell_type": "code",
"outputs": [],
"source": [
"HTML(\"\"\"\n",
"<script type = \"text/javascript\">\n",
@@ -79,39 +164,84 @@
" \"\"\".trimIndent())"
],
"metadata": {
"collapsed": false
"collapsed": false,
"ExecuteTime": {
"end_time": "2025-01-29T05:44:47.996860500Z",
"start_time": "2025-01-29T05:44:47.801222500Z"
}
},
"execution_count": null
"outputs": [
{
"data": {
"text/html": [
"<script type = \"text/javascript\">\n",
" element = document.getElementById(\"debug\")\n",
" element.append(window.Plotly)\n",
" element.append(window.plotlyConnect)\n",
"</script>"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"execution_count": 5
},
{
"cell_type": "code",
"outputs": [],
"source": [],
"metadata": {
"collapsed": false
"collapsed": false,
"ExecuteTime": {
"end_time": "2025-01-29T05:44:48.014863Z",
"start_time": "2025-01-29T05:44:48.010858500Z"
}
},
"outputs": [],
"execution_count": null
},
{
"cell_type": "code",
"outputs": [],
"source": [
"HTML(\"\"\"\n",
"<script src=\"https://cdn.plot.ly/plotly-2.29.1.min.js\" charset=\"utf-8\"></script>\n",
"\"\"\")"
],
"metadata": {
"collapsed": false
"collapsed": false,
"ExecuteTime": {
"end_time": "2025-01-29T05:44:48.097386400Z",
"start_time": "2025-01-29T05:44:48.036859600Z"
}
},
"execution_count": null
"outputs": [
{
"data": {
"text/html": [
"\n",
"<script src=\"https://cdn.plot.ly/plotly-2.29.1.min.js\" charset=\"utf-8\"></script>\n"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"execution_count": 7
},
{
"cell_type": "code",
"outputs": [],
"source": [],
"metadata": {
"collapsed": false
}
"collapsed": false,
"ExecuteTime": {
"end_time": "2025-01-29T05:44:48.109384Z",
"start_time": "2025-01-29T05:44:48.106385700Z"
}
},
"outputs": [],
"execution_count": null
}
],
"metadata": {
@@ -148,7 +278,7 @@
"toc_window_display": false
},
"ktnbPluginMetadata": {
"projectDependencies": true
"projectLibraries": false
}
},
"nbformat": 4,

View File

@@ -2,32 +2,43 @@
"cells": [
{
"cell_type": "code",
"execution_count": null,
"outputs": [],
"source": [
"import space.kscience.plotly.server.PlotlyServerIntegration\n",
"import space.kscience.plotly.server.jupyter\n",
"\n",
"USE(PlotlyServerIntegration())"
],
"metadata": {
"collapsed": false
}
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"source": [
"//import space.kscience.plotly.server.PlotlyServerIntegration\n",
"//import space.kscience.plotly.server.jupyter\n",
"//\n",
"//USE(PlotlyServerIntegration())\n",
"@file:CompilerArgs(\"-jvm-target=11\")\n",
"@file:Repository(\"https://repo.kotlin.link\")\n",
"@file:DependsOn(\"space.kscience:plotlykt-server-jvm:0.7.1.1\")"
],
"outputs": [],
"execution_count": null
},
{
"metadata": {},
"cell_type": "code",
"source": "Plotly.jupyter.notebook()",
"outputs": [],
"execution_count": null
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import kotlin.math.*"
]
],
"outputs": [],
"execution_count": null
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"val x1 = (0..100).map { it.toDouble() / 100.0 }\n",
"val y1 = x1.map { sin(2.0 * PI * it) }\n",
@@ -64,13 +75,13 @@
" }\n",
"}\n",
"fragment"
]
],
"outputs": [],
"execution_count": null
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"Plotly.plot {\n",
" traces(trace1, trace2)\n",
@@ -80,13 +91,13 @@
" yaxis.title = \"y axis name\"\n",
" }\n",
"}"
]
],
"outputs": [],
"execution_count": null
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"val x = (0..100).map { it.toDouble() / 100.0 }\n",
"val y = x.map { sin(2.0 * PI * it) }\n",
@@ -104,13 +115,13 @@
"}\n",
"\n",
"dynamicPlot"
]
],
"outputs": [],
"execution_count": null
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import kotlinx.coroutines.*\n",
"\n",
@@ -123,52 +134,57 @@
" trace.y.set(dynamicY)\n",
" }\n",
"}"
]
],
"outputs": [],
"execution_count": null
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"job.cancel()"
]
],
"outputs": [],
"execution_count": null
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"dynamicPlot.layout.xaxis.title = \"крокозябра\""
]
],
"outputs": [],
"execution_count": null
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"Plotly.jupyter.port = 8884"
]
],
"outputs": [],
"execution_count": null
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"Plotly.jupyter.port"
]
],
"outputs": [],
"execution_count": null
},
{
"cell_type": "code",
"execution_count": null,
"outputs": [],
"source": [],
"metadata": {
"collapsed": false
}
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"source": [],
"outputs": [],
"execution_count": null
}
],
"metadata": {
@@ -182,6 +198,9 @@
"language": "kotlin",
"name": "kotlin"
},
"ktnbPluginMetadata": {
"projectLibraries": false
},
"language_info": {
"codemirror_mode": "text/x-kotlin",
"file_extension": ".kt",
@@ -189,7 +208,7 @@
"name": "kotlin",
"nbconvert_exporter": "",
"pygments_lexer": "kotlin",
"version": "1.5.30-dev-598"
"version": "1.9.23"
},
"toc": {
"base_numbering": 1,
@@ -203,9 +222,6 @@
"toc_position": {},
"toc_section_display": false,
"toc_window_display": false
},
"ktnbPluginMetadata": {
"projectDependencies": true
}
},
"nbformat": 4,

View File

@@ -8,20 +8,13 @@ import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.node
import space.kscience.dataforge.misc.DFBuilder
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
import space.kscience.plotly.models.Layout
import space.kscience.plotly.models.Trace
import space.kscience.visionforge.*
import kotlin.properties.ReadWriteProperty
/**
* A temporary plug until DataForge 0.9.1
*
*/
internal fun <T : Scheme> MutableVision.scheme(
spec: SchemeSpec<T>,
key: Name? = null
): ReadWriteProperty<Any?, T> = properties.scheme(spec, key)
import space.kscience.visionforge.AbstractVision
import space.kscience.visionforge.VisionEvent
import space.kscience.visionforge.VisionGroup
import space.kscience.visionforge.VisionGroupCompositionChangedEvent
/**
* The main plot class.
@@ -34,7 +27,7 @@ public class Plot : AbstractVision(), VisionGroup<Trace> {
private val _data = mutableListOf<Trace>()
public val data: List<Trace> get() = _data
override val items: Map<Name, Trace>
override val visions: Map<NameToken, Trace>
get() = data.associateBy { it.uid }
override suspend fun receiveEvent(event: VisionEvent) {
@@ -44,7 +37,7 @@ public class Plot : AbstractVision(), VisionGroup<Trace> {
/**
* Layout specification for th plot
*/
public val layout: Layout by scheme(Layout)
public val layout: Layout by properties.scheme(Layout)
public fun addTrace(trace: Trace) {
_data.add(trace)

View File

@@ -3,10 +3,10 @@ package space.kscience.plotly.models
import space.kscience.dataforge.meta.enum
import space.kscience.dataforge.meta.number
import space.kscience.dataforge.meta.numberList
import space.kscience.dataforge.meta.scheme
import space.kscience.dataforge.names.asName
import space.kscience.plotly.Plot
import space.kscience.plotly.numberGreaterThan
import space.kscience.plotly.scheme
public class Bar : Trace(), SelectedPoints {
init {

View File

@@ -1,10 +1,10 @@
package space.kscience.plotly.models
import space.kscience.dataforge.meta.Value
import space.kscience.dataforge.meta.boolean
import space.kscience.dataforge.meta.enum
import space.kscience.dataforge.meta.numberList
import space.kscience.plotly.*
import space.kscience.dataforge.meta.*
import space.kscience.plotly.Plot
import space.kscience.plotly.doubleInRange
import space.kscience.plotly.listOfValues
import space.kscience.plotly.numberGreaterThan
import kotlin.js.JsName
public enum class BoxMean {

View File

@@ -5,7 +5,6 @@ import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.plotly.Plot
import space.kscience.plotly.doubleInRange
import space.kscience.plotly.scheme
public enum class XPeriodAlignment{

View File

@@ -2,9 +2,9 @@ package space.kscience.plotly.models
import space.kscience.dataforge.meta.boolean
import space.kscience.dataforge.meta.enum
import space.kscience.dataforge.meta.scheme
import space.kscience.plotly.Plot
import space.kscience.plotly.intGreaterThan
import space.kscience.plotly.scheme
public class Contour : Trace(), HeatmapContour, ContourSpec {
init {

View File

@@ -4,7 +4,6 @@ import space.kscience.dataforge.meta.*
import space.kscience.plotly.Plot
import space.kscience.plotly.intGreaterThan
import space.kscience.plotly.numberGreaterThan
import space.kscience.plotly.scheme
import kotlin.js.JsName

View File

@@ -2,9 +2,9 @@ package space.kscience.plotly.models
import space.kscience.dataforge.meta.enum
import space.kscience.dataforge.meta.numberList
import space.kscience.dataforge.meta.scheme
import space.kscience.dataforge.meta.string
import space.kscience.plotly.Plot
import space.kscience.plotly.scheme
import kotlin.js.JsName
public enum class ScatterMode {

View File

@@ -4,7 +4,6 @@ import space.kscience.dataforge.meta.*
import space.kscience.dataforge.names.asName
import space.kscience.plotly.Plot
import space.kscience.plotly.numberGreaterThan
import space.kscience.plotly.scheme
/**
* Scheme to define table cell colors.

View File

@@ -722,7 +722,7 @@ public class Hoverlabel : Scheme() {
*/
@Serializable
public open class Trace(
@Transient internal val uid: Name = NameToken("trace", uuid4().leastSignificantBits.toString(16)).asName()
@Transient internal val uid: NameToken = NameToken("trace", uuid4().leastSignificantBits.toString(16))
) : AbstractVision(), MutableMetaProvider, MetaRepr {
override fun get(name: Name): MutableMeta? = properties.get(name)
@@ -735,7 +735,7 @@ public open class Trace(
properties.setValue(name, value)
}
override fun toMeta(): Meta = properties
override fun toMeta(): Meta = properties
public fun axis(axisName: String): TraceValues = TraceValues(this, Name.parse(axisName))
@@ -1042,16 +1042,16 @@ public open class Trace(
}
}
public inline fun Trace(block: Trace.()->Unit): Trace = Trace().apply(block)
public inline fun Trace(block: Trace.() -> Unit): Trace = Trace().apply(block)
//public operator fun <T : Trace> SchemeSpec<T>.invoke(
// xs: Any,
// ys: Any? = null,
// zs: Any? = null,
// block: Trace.() -> Unit,
//): T = invoke {
// x.set(xs)
// if (ys != null) y.set(ys)
// if (zs != null) z.set(zs)
// block()
//}
public fun Trace(
xs: Any,
ys: Any? = null,
zs: Any? = null,
block: Trace.() -> Unit,
): Trace = Trace {
x.set(xs)
if (ys != null) y.set(ys)
if (zs != null) z.set(zs)
block()
}

View File

@@ -9,7 +9,7 @@ import java.nio.file.Path
import java.nio.file.StandardOpenOption
internal const val PLOTLY_SCRIPT_PATH = "/js/plotly.min.js"
internal const val PLOTLY_SCRIPT_PATH = "/js/plotly-kt.js"
//const val PLOTLY_PROMISE_NAME = "promiseOfPlotly"
/**
* Check if the asset exists in given local location and put it there if it does not

View File

@@ -3,15 +3,14 @@ package bootstrap
import androidx.compose.runtime.*
import org.jetbrains.compose.web.attributes.Scope
import org.jetbrains.compose.web.attributes.scope
import org.jetbrains.compose.web.css.*
import org.jetbrains.compose.web.css.CSSLengthOrPercentageValue
import org.jetbrains.compose.web.css.StyleScope
import org.jetbrains.compose.web.css.top
import org.jetbrains.compose.web.dom.*
import org.w3c.dom.HTMLDivElement
import org.w3c.dom.HTMLTableCaptionElement
import org.w3c.dom.HTMLTableCellElement
import org.w3c.dom.HTMLTableElement
import kotlin.collections.component1
import kotlin.collections.component2
import kotlin.collections.set
import kotlin.math.max
import kotlin.math.min
@@ -31,16 +30,16 @@ public object Table {
val footer: Footer?
)
public data class Cell internal constructor(
public class Cell internal constructor(
public val color: Color? = null,
val scope: Scope?,
val verticalAlignment: Layout.VerticalAlignment?,
val content: ContentBuilder<HTMLTableCellElement>
public val scope: Scope?,
public val verticalAlignment: Layout.VerticalAlignment?,
public val content: ContentBuilder<HTMLTableCellElement>
)
public data class Footer internal constructor(
public class Footer internal constructor(
public val color: Color? = null,
val content: @Composable ElementScope<HTMLTableCellElement>.(List<Cell>) -> Unit
public val content: @Composable ElementScope<HTMLTableCellElement>.(List<Cell>) -> Unit
)
public data class Header(

View File

@@ -7,7 +7,10 @@ import org.jetbrains.compose.web.css.cursor
import org.jetbrains.compose.web.dom.Div
import org.jetbrains.compose.web.dom.Span
import org.jetbrains.compose.web.dom.Text
import space.kscience.dataforge.names.*
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.lastOrNull
import space.kscience.dataforge.names.plus
import space.kscience.dataforge.names.startsWith
import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionGroup
@@ -46,7 +49,7 @@ public fun VisionTree(
//display as node if any child is visible
if (vision is VisionGroup<*>) {
FlexRow {
if (vision.items.keys.any { !it.first().body.startsWith("@") }) {
if (vision.visions.keys.any { !it.body.startsWith("@") }) {
Span({
classes(TreeStyles.treeCaret)
if (expanded) {
@@ -63,9 +66,9 @@ public fun VisionTree(
FlexColumn({
classes(TreeStyles.tree)
}) {
vision.items.asSequence()
vision.visions.asSequence()
.filter { !it.key.toString().startsWith("@") } // ignore statics and other hidden children
.sortedBy { (it.value as? VisionGroup<Vision>)?.items?.isEmpty() ?: true } // ignore empty groups
.sortedBy { (it.value as? VisionGroup<Vision>)?.visions?.isEmpty() ?: true } // ignore empty groups
.forEach { (childToken, child) ->
Div({ classes(TreeStyles.treeItem) }) {
VisionTree(

View File

@@ -50,7 +50,7 @@ public class ComposeVisionClient : AbstractPlugin(), VisionClient {
override fun notifyPropertyChanged(visionName: Name, propertyName: Name, item: Meta?) {
context.launch {
mutex.withLock {
rootChangeCollector.propertyChanged(visionName, propertyName, item)
rootChangeCollector.getOrCreateChange(visionName).propertyChanged(propertyName, item)
}
}
}

View File

@@ -16,7 +16,7 @@ kscience {
api(spclibs.kotlinx.html)
}
jsMain {
api("org.jetbrains.kotlin-wrappers:kotlin-extensions")
api("org.jetbrains.kotlin-wrappers:kotlin-js")
}
useSerialization {
json()

View File

@@ -5,7 +5,7 @@ import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.plus
/**
* A [Meta] child proxy that creates required node on write
* A [Meta] child proxy that creates required nodes on write
*/
public class MutableMetaProxy(
public val upstream: MutableMeta,

View File

@@ -4,7 +4,6 @@ import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.launch
import kotlinx.serialization.EncodeDefault
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
@@ -22,17 +21,21 @@ public abstract class AbstractVision(
) : MutableVision {
@Transient
private val _eventFlow = MutableSharedFlow<VisionEvent>(1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
private val _eventFlow =
MutableSharedFlow<VisionEvent>(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
override val eventFlow: SharedFlow<VisionEvent> get() = _eventFlow
protected fun emitEvent(event: VisionEvent) {
val context = manager?.context
if (context == null) {
_eventFlow.tryEmit(event)
} else {
context.launch {
_eventFlow.emit(event)
}
}
_eventFlow.tryEmit(event)
// val context = manager?.context
// if (context == null) {
// _eventFlow.tryEmit(event)
// } else {
// context.launch {
// _eventFlow.emit(event)
// }
// }
}
init {
@@ -45,12 +48,12 @@ public abstract class AbstractVision(
}
}
override val eventFlow: SharedFlow<VisionEvent> get() = _eventFlow
@Transient
override var parent: Vision? = null
set(value) {
if (parent != null && field != null) {
if (value == this) {
error("Circular parent")
} else if (value != null && field != null && field != value) {
error("Parent is already set")
} else {
field = value

View File

@@ -55,7 +55,7 @@ public abstract class AbstractControlVision : AbstractVision(), ControlVision {
*/
@Serializable
@SerialName("control.submit")
public class VisionSubmitEvent(override val meta: Meta) : VisionControlEvent() {
public class ControlSubmitEvent(override val meta: Meta) : VisionControlEvent() {
public val payload: Meta get() = meta[::payload.name] ?: Meta.EMPTY
public val name: Name? get() = meta["name"].string?.parseAsName()
@@ -63,10 +63,10 @@ public class VisionSubmitEvent(override val meta: Meta) : VisionControlEvent() {
override fun toString(): String = meta.toString()
}
public fun VisionSubmitEvent(payload: Meta = Meta.EMPTY, name: Name? = null): VisionSubmitEvent = VisionSubmitEvent(
public fun ControlSubmitEvent(payload: Meta = Meta.EMPTY, name: Name? = null): ControlSubmitEvent = ControlSubmitEvent(
Meta {
VisionSubmitEvent::payload.name put payload
VisionSubmitEvent::name.name put name.toString()
ControlSubmitEvent::payload.name put payload
ControlSubmitEvent::name.name put name.toString()
}
)
@@ -76,20 +76,20 @@ public interface DataControl : ControlVision {
* Create and dispatch submit event
*/
public suspend fun submit(builder: MutableMeta.() -> Unit = {}) {
dispatchControlEvent(VisionSubmitEvent(Meta(builder)))
dispatchControlEvent(ControlSubmitEvent(Meta(builder)))
}
}
/**
* Register listener
*/
public fun DataControl.onSubmit(scope: CoroutineScope, block: suspend VisionSubmitEvent.() -> Unit): Job =
eventFlow.filterIsInstance<VisionSubmitEvent>().onEach(block).launchIn(scope)
public fun DataControl.onSubmit(scope: CoroutineScope, block: suspend ControlSubmitEvent.() -> Unit): Job =
eventFlow.filterIsInstance<ControlSubmitEvent>().onEach(block).launchIn(scope)
@Serializable
@SerialName("control.valueChange")
public class VisionValueChangeEvent(override val meta: Meta) : VisionControlEvent() {
public class ControlValueChangeEvent(override val meta: Meta) : VisionControlEvent() {
public val value: Value? get() = meta.value
@@ -102,7 +102,7 @@ public class VisionValueChangeEvent(override val meta: Meta) : VisionControlEven
}
public fun VisionValueChangeEvent(value: Value?, name: Name? = null): VisionValueChangeEvent = VisionValueChangeEvent(
public fun ControlValueChangeEvent(value: Value?, name: Name? = null): ControlValueChangeEvent = ControlValueChangeEvent(
Meta {
this.value = value
name?.let { set("name", it.toString()) }
@@ -112,7 +112,7 @@ public fun VisionValueChangeEvent(value: Value?, name: Name? = null): VisionValu
@Serializable
@SerialName("control.input")
public class VisionInputEvent(override val meta: Meta) : VisionControlEvent() {
public class ControlInputEvent(override val meta: Meta) : VisionControlEvent() {
public val value: Value? get() = meta.value
@@ -124,7 +124,7 @@ public class VisionInputEvent(override val meta: Meta) : VisionControlEvent() {
override fun toString(): String = meta.toString()
}
public fun VisionInputEvent(value: Value?, name: Name? = null): VisionInputEvent = VisionInputEvent(
public fun ControlInputEvent(value: Value?, name: Name? = null): ControlInputEvent = ControlInputEvent(
Meta {
this.value = value
name?.let { set("name", it.toString()) }

View File

@@ -9,9 +9,7 @@ import kotlinx.serialization.SerialName
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
import space.kscience.dataforge.names.*
import kotlin.time.Duration
@@ -47,25 +45,29 @@ private fun Vision.deepCopy(manager: VisionManager): Vision {
}
/**
* An event that contains changes made to a 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].
*/
@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
///**
// * A listener that listens to both current vision property changes and to children changes
// * An event that contains changes made to a 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].
// */
//public interface VisionGroupListener : VisionListener, MutableVisionContainer<Vision>
//@Serializable
//@SerialName("change")
//public data class VisionChange(
// public val vision: Vision? = null,
// public val properties: Meta? = null,
// public val children: Map<NameToken, VisionChange>? = null,
//) : VisionEvent
@Serializable
public sealed interface ChangeVisionEvent : VisionEvent
@Serializable
public data class SetVisionPropertiesEvent(val properties: Meta) : ChangeVisionEvent
@Serializable
public data class SetVisionChildEvent(val nameToken: NameToken, val vision: Vision?) : ChangeVisionEvent
/**
@@ -75,19 +77,15 @@ public class VisionChangeBuilder : MutableVisionContainer<Vision> {
private var vision: Vision? = null
private var propertyChange = MutableMeta()
private val children: HashMap<Name, VisionChangeBuilder> = HashMap()
private val children: HashMap<NameToken, VisionChangeBuilder> = HashMap()
public operator fun get(name: Name): VisionChangeBuilder? = children[name]
public operator fun get(name: NameToken): VisionChangeBuilder? = children[name]
public fun isEmpty(): Boolean = propertyChange.isEmpty() && propertyChange.isEmpty() && children.isEmpty()
@JvmSynchronized
private fun getOrPutChild(visionName: Name): VisionChangeBuilder =
if (visionName.isEmpty()) {
this
} else {
children.getOrPut(visionName) { VisionChangeBuilder() }
}
public fun getOrCreateChange(token: NameToken): VisionChangeBuilder =
children.getOrPut(token) { VisionChangeBuilder() }
@JvmSynchronized
internal fun reset() {
@@ -96,119 +94,90 @@ public class VisionChangeBuilder : MutableVisionContainer<Vision> {
children.clear()
}
public fun propertyChanged(visionName: Name, propertyName: Name, item: Meta?) {
if (visionName == Name.EMPTY) {
//Write property removal as [Null]
if (propertyName.isEmpty()) {
propertyChange = item?.toMutableMeta() ?: MutableMeta()
} else {
propertyChange[propertyName] = (item ?: Meta(Null))
}
public fun propertyChanged(propertyName: Name, item: Meta?) {
//Write property removal as [Null]
if (propertyName.isEmpty()) {
propertyChange = item?.toMutableMeta() ?: MutableMeta()
} else {
getOrPutChild(visionName).propertyChanged(Name.EMPTY, propertyName, item)
propertyChange[propertyName] = (item ?: Meta(Null))
}
}
override fun setVision(name: Name, vision: Vision?) {
getOrPutChild(name).apply {
override fun setVision(token: NameToken, vision: Vision?) {
getOrCreateChange(token).apply {
this.vision = vision ?: NullVision
}
}
private fun updateFrom(baseName: Name, change: VisionChange) {
getOrPutChild(baseName).apply {
change.vision?.let { this.vision = it }
change.properties?.let { this.propertyChange.update(it) }
change.children?.let { it.forEach { (key, change) -> updateFrom(baseName + key, change) } }
}
}
// private fun updateFrom(change: VisionChange) {
// change.vision?.let { this.vision = it }
// change.properties?.let { this.propertyChange.update(it) }
// change.children?.let { it.forEach { (key, change) -> getOrCreateChange(key).updateFrom(change) } }
// }
public fun consumeEvent(event: VisionEvent): Unit = when (event) {
is VisionChange -> updateFrom(Name.EMPTY, event)
//is VisionChange -> updateFrom(event)
is VisionEventCollection -> event.events.forEach { consumeEvent(it) }
is VisionChildEvent -> TODO()
is SetVisionChildEvent -> setVision(event.nameToken, event.vision)
is SetVisionPropertiesEvent -> TODO()
//listen to changed event
is VisionPropertyChangedEvent -> propertyChanged(
visionName = Name.EMPTY,
propertyName = event.propertyName,
item = event.source.properties[event.propertyName]
)
// is VisionGroupPropertyChangedEvent -> propertyChanged(
// visionName = event.childName,
// propertyName = event.propertyName,
// item = event.source.getVision(event.childName)?.properties?.get(event.propertyName)
// )
is VisionGroupCompositionChangedEvent -> setVision(event.childName, event.source.getVision(event.childName))
is VisionControlEvent, is VisionMetaEvent -> {
//do nothing
//TODO add logging
}
}
private fun build(visionManager: VisionManager): VisionChange = VisionChange(
vision,
if (propertyChange.isEmpty()) null else propertyChange,
if (children.isEmpty()) null else children.mapValues { it.value.build(visionManager) }
)
/**
* Isolate collected changes by creating detached copies of given visions
*/
public fun deepCopy(visionManager: VisionManager): VisionChange = VisionChange(
vision?.deepCopy(visionManager),
if (propertyChange.isEmpty()) null else propertyChange.seal(),
if (children.isEmpty()) null else children.mapValues { it.value.deepCopy(visionManager) }
)
/**
* Transform current change directly to Json string without protective copy
*/
public fun toJsonString(visionManager: VisionManager): String = visionManager.encodeToString(
build(visionManager)
)
// private fun build(visionManager: VisionManager): VisionChange = VisionChange(
// vision,
// if (propertyChange.isEmpty()) null else propertyChange,
// if (children.isEmpty()) null else children.mapValues { it.value.build(visionManager) }
// )
//
// /**
// * Isolate collected changes by creating detached copies of given visions
// */
// public fun deepCopy(visionManager: VisionManager): VisionChange = VisionChange(
// vision?.deepCopy(visionManager),
// if (propertyChange.isEmpty()) null else propertyChange.seal(),
// if (children.isEmpty()) null else children.mapValues { it.value.deepCopy(visionManager) }
// )
//
// /**
// * Transform current change directly to Json string without protective copy
// */
// public fun toJsonString(visionManager: VisionManager): String = visionManager.encodeToString(
// build(visionManager)
// )
}
public inline fun VisionManager.VisionChange(block: VisionChangeBuilder.() -> Unit): VisionChange =
public operator fun VisionChangeBuilder.get(name: Name): VisionChangeBuilder? = when (name.length) {
0 -> this
1 -> get(name.first())
else -> get(name.first())?.get(name.cutFirst())
}
public fun VisionChangeBuilder.getOrCreateChange(name: Name): VisionChangeBuilder = when (name.length) {
0 -> this
1 -> getOrCreateChange(name.first())
else -> getOrCreateChange(name.first()).getOrCreateChange(name.cutFirst())
}
public inline fun VisionManager.VisionChange(block: VisionChangeBuilder.() -> Unit): VisionEvent =
VisionChangeBuilder().apply(block).deepCopy(this)
///**
// * Collect changes that are made to [source] to [collector] using [mutex] as a synchronization lock.
// */
//private fun CoroutineScope.collectChange(
// name: Name,
// source: Vision,
// mutex: Mutex,
// collector: VisionChangeBuilder,
//) {
//
// source.listen(this, collector)
// //Collect properties change
// source.properties.changes.onEach { propertyName ->
// val newItem = source.properties.own[propertyName]
// collector.propertyChanged(name, propertyName, newItem)
// }.launchIn(this)
//
// val children = source.children
// //Subscribe for children changes
// children?.forEach { token, child ->
// collectChange(name + token, child, mutex, collector)
// }
//
// //Subscribe for structure change
// children?.changes?.onEach { changedName ->
// val after = children[changedName]
// val fullName = name + changedName
// if (after != null) {
// collectChange(fullName, after, mutex, collector)
// }
// mutex.withLock {
// collector.setVision(fullName, after)
// }
// }?.launchIn(this)
//}
/**
* Generate a flow of changes of this vision and its children
*
@@ -217,7 +186,7 @@ public inline fun VisionManager.VisionChange(block: VisionChangeBuilder.() -> Un
public fun Vision.flowChanges(
collectionDuration: Duration,
sendInitial: Boolean = false,
): Flow<VisionChange> = flow {
): Flow<VisionEvent> = flow {
val manager = manager ?: error("Orphan vision could not collect changes")
coroutineScope {
val collector = VisionChangeBuilder()

View File

@@ -2,7 +2,7 @@ package space.kscience.visionforge
import com.benasher44.uuid.uuid4
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.parseAsName
import space.kscience.dataforge.names.NameToken
@DslMarker
public annotation class VisionBuilder
@@ -12,23 +12,19 @@ public annotation class VisionBuilder
* using DataForge [Name] objects as keys.
*/
public interface VisionContainer<out V : Vision> {
public fun getVision(name: Name): V?
public fun getVision(token: NameToken): V?
}
public fun <V : Vision> VisionContainer<V>.getVision(name: String): V? = getVision(name.parseAsName())
public fun <V : Vision> VisionContainer<V>.getVision(token: String): V? = getVision(NameToken.parse(token))
/**
* A container interface with write/replace/delete access to its content.
*/
public interface MutableVisionContainer<in V : Vision> {
//TODO add documentation
public fun setVision(name: Name, vision: V?)
public fun setVision(token: NameToken, vision: V?)
public companion object {
public fun generateID(): String = "@vision[${uuid4().leastSignificantBits.toString(16)}]"
public fun generateID(): NameToken = NameToken("@vision",uuid4().leastSignificantBits.toString(16))
}
}
public fun <V : Vision> MutableVisionContainer<V>.setVision(name: String, vision: V?) {
setVision(name.parseAsName(), vision)
}
}

View File

@@ -5,6 +5,7 @@ import kotlinx.serialization.Serializable
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaRepr
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
/**
* An event propagated from client to a server or vise versa
@@ -16,12 +17,36 @@ public sealed interface VisionEvent {
}
/**
* An event that designates that property value is invalidated (not necessary changed
* A list of [VisionEvent] that are delivered at the same time
*/
@Serializable
public class VisionEventCollection(public val events: List<VisionEvent>): VisionEvent
/**
* An event that should be forwarded to a [Vision] child
*/
@Serializable
public class VisionChildEvent(public val childName: Name, public val event: VisionEvent): VisionEvent
public sealed interface VisionChangedEvent: VisionEvent
/**
* An event that designates that property value is invalidated (not necessarily changed)
*/
public data class VisionPropertyChangedEvent(
public val source: Vision,
public val propertyName: Name
): VisionEvent
): VisionChangedEvent
/**
* An event that indicates that [VisionGroup] composition is invalidated (not necessarily changed)
*/
public data class VisionGroupCompositionChangedEvent(
public val source: VisionContainer<*>,
public val childName: NameToken
) : VisionChangedEvent
/**
* An event that consists of custom meta

View File

@@ -7,22 +7,19 @@ import space.kscience.dataforge.meta.ValueType
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.value
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.parseAsName
import space.kscience.dataforge.names.plus
import space.kscience.visionforge.SimpleVisionGroup.Companion.updateProperties
import space.kscience.visionforge.Vision.Companion.STYLE_KEY
import space.kscience.visionforge.Vision.Companion.VISION_PROPERTY_TARGET
import kotlin.collections.component1
import kotlin.collections.component2
import kotlin.collections.set
public interface VisionGroup<out V : Vision> : Vision, VisionContainer<V> {
public val items: Map<Name, V>
public val visions: Map<NameToken, V>
override fun getVision(name: Name): V? = items[name]
override fun getVision(token: NameToken): V? = visions[token]
override suspend fun receiveEvent(event: VisionEvent) {
super.receiveEvent(event)
@@ -43,7 +40,7 @@ public interface VisionGroup<out V : Vision> : Vision, VisionContainer<V> {
override fun content(target: String): Map<Name, Any> = when (target) {
VISION_PROPERTY_TARGET -> readProperties().items.entries.associate { it.key.asName() to it.value }
VISION_CHILD_TARGET -> items
VISION_CHILD_TARGET -> visions.mapKeys { it.key.asName() }
else -> emptyMap()
}
@@ -52,14 +49,6 @@ public interface VisionGroup<out V : Vision> : Vision, VisionContainer<V> {
}
}
/**
* An event that indicates that [VisionGroup] composition is invalidated (not necessarily changed
*/
public data class VisionGroupCompositionChangedEvent(
public val source: VisionContainer<*>,
public val childName: Name
) : VisionEvent
///**
// * An event that indicates that child property value has been invalidated
// */
@@ -78,26 +67,25 @@ public interface MutableVisionGroup<V : Vision> : VisionGroup<V>, MutableVision,
public fun convertVisionOrNull(vision: Vision): V?
override suspend fun receiveEvent(event: VisionEvent) {
if (event is VisionChange) {
event.properties?.let {
updateProperties(it, Name.EMPTY)
}
event.children?.forEach { (name, change) ->
change.children?.forEach { (name, change) ->
when {
change.vision == NullVision -> setVision(name, null)
change.vision != null -> setVision(
name,
convertVisionOrNull(change.vision) ?: error("Can't convert ${change.vision}")
)
event.children?.forEach { (childName, change) ->
when {
change.vision == NullVision -> setVision(childName, null)
else -> getVision(name)?.receiveEvent(change)
}
}
change.properties?.let {
updateProperties(it, Name.EMPTY)
change.vision != null -> setVision(
childName,
convertVisionOrNull(change.vision) ?: error("Can't convert ${change.vision}")
)
else -> getVision(childName)?.receiveEvent(change)
}
}
} else {
super<MutableVision>.receiveEvent(event)
}
}
}
@@ -111,20 +99,20 @@ public class SimpleVisionGroup : AbstractVision(), MutableVisionGroup<Vision> {
@Serializable
@SerialName("children")
private val _items = mutableMapOf<Name, Vision>()
private val _items = mutableMapOf<NameToken, Vision>()
override val items: Map<Name, Vision> get() = _items
override val visions: Map<NameToken, Vision> get() = _items
override fun convertVisionOrNull(vision: Vision): Vision = vision
override fun setVision(name: Name, vision: Vision?) {
override fun setVision(token: NameToken, vision: Vision?) {
if (vision == null) {
_items.remove(name)
_items.remove(token)
} else {
_items[name] = vision
_items[token] = vision
vision.parent = this
}
emitEvent(VisionGroupCompositionChangedEvent(this, name))
emitEvent(VisionGroupCompositionChangedEvent(this, token))
}
public companion object {
@@ -146,20 +134,20 @@ public class SimpleVisionGroup : AbstractVision(), MutableVisionGroup<Vision> {
@VisionBuilder
public inline fun MutableVisionContainer<Vision>.group(
name: Name? = null,
name: NameToken? = null,
builder: SimpleVisionGroup.() -> Unit = {},
): SimpleVisionGroup = SimpleVisionGroup().also {
setVision(name ?: MutableVisionContainer.generateID().asName(), it)
setVision(name ?: MutableVisionContainer.generateID(), it)
}.apply(builder)
/**
* Define a group with given [name], attach it to this parent and return it.
* Define a group with given [token], attach it to this parent and return it.
*/
@VisionBuilder
public inline fun MutableVisionContainer<Vision>.group(
name: String,
token: String,
builder: SimpleVisionGroup.() -> Unit = {},
): SimpleVisionGroup = group(name.parseAsName(), builder)
): SimpleVisionGroup = group(NameToken.parse(token), builder)
public fun VisionGroup(
parent: Vision? = null,

View File

@@ -42,8 +42,9 @@ public class VisionManager(meta: Meta) : AbstractPlugin(meta), Vision {
public fun decodeFromString(string: String): Vision = jsonFormat.decodeFromString(visionSerializer, string)
public fun encodeToString(vision: Vision): String = jsonFormat.encodeToString(visionSerializer, vision)
public fun encodeToString(change: VisionChange): String =
jsonFormat.encodeToString(VisionChange.serializer(), change)
// public fun encodeToString(change: VisionChange): String =
// jsonFormat.encodeToString(VisionChange.serializer(), change)
public fun decodeFromJson(json: JsonElement): Vision = jsonFormat.decodeFromJsonElement(visionSerializer, json)
@@ -94,11 +95,14 @@ public class VisionManager(meta: Meta) : AbstractPlugin(meta), Vision {
}
polymorphic(VisionEvent::class) {
subclass(VisionChange.serializer())
subclass(VisionEventCollection.serializer())
subclass(VisionChildEvent.serializer())
subclass(SetVisionPropertiesEvent.serializer())
subclass(SetVisionChildEvent.serializer())
subclass(VisionMetaEvent.serializer())
subclass(VisionSubmitEvent.serializer())
subclass(VisionValueChangeEvent.serializer())
subclass(VisionInputEvent.serializer())
subclass(ControlSubmitEvent.serializer())
subclass(ControlValueChangeEvent.serializer())
subclass(ControlInputEvent.serializer())
}
}

View File

@@ -87,13 +87,13 @@ public open class VisionOfHtmlInput(
*/
public fun VisionOfHtmlInput.onValueChange(
scope: CoroutineScope = manager?.context ?: error("Coroutine context is not resolved for $this"),
callback: suspend VisionValueChangeEvent.() -> Unit,
): Job = eventFlow.filterIsInstance<VisionValueChangeEvent>().onEach(callback).launchIn(scope)
callback: suspend ControlValueChangeEvent.() -> Unit,
): Job = eventFlow.filterIsInstance<ControlValueChangeEvent>().onEach(callback).launchIn(scope)
public fun VisionOfHtmlInput.onInput(
scope: CoroutineScope = manager?.context ?: error("Coroutine context is not resolved for $this"),
callback: suspend VisionInputEvent.() -> Unit,
): Job = eventFlow.filterIsInstance<VisionInputEvent>().onEach(callback).launchIn(scope)
callback: suspend ControlInputEvent.() -> Unit,
): Job = eventFlow.filterIsInstance<ControlInputEvent>().onEach(callback).launchIn(scope)
@Suppress("UnusedReceiverParameter")
public inline fun VisionOutput.htmlInput(

View File

@@ -38,7 +38,7 @@ public interface VisionVisitor {
visionVisitor.visitChildren(name, vision)
if (vision is VisionGroup<*>) {
vision.items.forEach { (token, child) ->
vision.visions.forEach { (token, child) ->
visitTreeAsync(visionVisitor, name + token, child)
}
}

View File

@@ -16,7 +16,6 @@ import space.kscience.visionforge.*
import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
import kotlin.time.Duration.Companion.milliseconds
@@ -36,8 +35,8 @@ internal class VisionPropertyTest {
vision.properties["fff"] = 2
vision.properties["fff.ddd"] = false
assertEquals(2, vision.properties.getValue("fff")?.int)
assertEquals(false, vision.properties.getValue("fff.ddd")?.boolean)
assertEquals(2, vision.readProperty("fff")?.int)
assertEquals(false, vision.readProperty("fff.ddd")?.boolean)
}
@Test
@@ -46,8 +45,8 @@ internal class VisionPropertyTest {
vision.writeProperty("fff.ddd".parseAsName()).apply {
value = 2.asValue()
}
assertEquals(2, vision.properties.getValue("fff.ddd")?.int)
assertNotEquals(true, vision.properties.getValue("fff.ddd")?.boolean)
assertEquals(2, vision.readProperty("fff.ddd")?.int)
assertEquals(true, vision.readProperty("fff.ddd")?.boolean)
}
@Test
@@ -56,7 +55,7 @@ internal class VisionPropertyTest {
vision.writeProperty("fff".asName()).updateWith(TestScheme) {
ddd = 2
}
assertEquals(2, vision.properties.getValue("fff.ddd")?.int)
assertEquals(2, vision.readProperty("fff.ddd")?.int)
}
@Test
@@ -72,7 +71,7 @@ internal class VisionPropertyTest {
}
}
val child = group.items["child"] as MutableVision
val child = group.visions["child"] as MutableVision
val deferred: CompletableDeferred<Value?> = CompletableDeferred()
@@ -111,7 +110,7 @@ internal class VisionPropertyTest {
}
val child = group.items["child"] as MutableVision
val child = group.visions["child"] as MutableVision
val semaphore = Semaphore(1, 1)

View File

@@ -78,7 +78,7 @@ public class JsVisionClient : AbstractPlugin(), VisionClient {
override fun notifyPropertyChanged(visionName: Name, propertyName: Name, item: Meta?) {
context.launch {
mutex.withLock {
rootChangeCollector.propertyChanged(visionName, propertyName, item)
rootChangeCollector.getOrCreateChange(visionName).propertyChanged(propertyName, item)
}
}
}
@@ -150,7 +150,7 @@ public class JsVisionClient : AbstractPlugin(), VisionClient {
//aggregate atomic changes
while (isActive) {
delay(feedbackAggregationTime.milliseconds)
val visionChangeCollector = rootChangeCollector[name]
val visionChangeCollector = rootChangeCollector[visionName]
if (visionChangeCollector?.isEmpty() == false) {
mutex.withLock {
eventCollector.emit(visionName to visionChangeCollector.deepCopy(visionManager))
@@ -166,6 +166,7 @@ public class JsVisionClient : AbstractPlugin(), VisionClient {
feedbackJob?.cancel()
logger.info { "WebSocket feedback channel closed for output '$visionName'" }
}
onerror = {
feedbackJob?.cancel()
logger.error { "WebSocket feedback channel error for output '$visionName'" }
@@ -181,12 +182,12 @@ public class JsVisionClient : AbstractPlugin(), VisionClient {
findRendererFor(vision) ?: error("Could not find renderer for ${vision::class}")
//render vision
renderer.render(element, name, vision, outputMeta)
//start vision update from backend model
//start vision update from a backend model
startVisionUpdate(element, name, vision, outputMeta)
//subscribe to a backwards events propagation for control visions
if(vision is ControlVision){
//subscribe to backwards events propagation for control visions
if (vision is ControlVision) {
vision.eventFlow.onEach {
sendEvent(name,it)
sendEvent(name, it)
}.launchIn(context)
}

View File

@@ -68,7 +68,7 @@ internal val formVisionRenderer: ElementVisionRenderer =
form.onsubmit = { event ->
event.preventDefault()
val formData = FormData(form).toMeta()
vision.asyncControlEvent(VisionSubmitEvent(name = name, payload = formData))
vision.asyncControlEvent(ControlSubmitEvent(name = name, payload = formData))
console.info("Sent form data: ${formData.toMap()}")
false
}
@@ -79,7 +79,7 @@ internal val buttonVisionRenderer: ElementVisionRenderer =
button(type = ButtonType.button).also { button ->
button.subscribeToVision(vision)
button.onclick = {
vision.asyncControlEvent(VisionSubmitEvent(name = name))
vision.asyncControlEvent(ControlSubmitEvent(name = name))
}
vision.useProperty(VisionOfHtmlButton::label) {
button.innerHTML = it ?: ""

View File

@@ -9,8 +9,8 @@ import org.w3c.dom.HTMLInputElement
import space.kscience.dataforge.meta.asValue
import space.kscience.dataforge.meta.double
import space.kscience.dataforge.meta.string
import space.kscience.visionforge.VisionInputEvent
import space.kscience.visionforge.VisionValueChangeEvent
import space.kscience.visionforge.ControlInputEvent
import space.kscience.visionforge.ControlValueChangeEvent
import space.kscience.visionforge.asyncControlEvent
import space.kscience.visionforge.useProperty
@@ -60,11 +60,11 @@ internal val inputVisionRenderer: ElementVisionRenderer = ElementVisionRenderer<
}.also { htmlInputElement ->
htmlInputElement.onchange = {
vision.asyncControlEvent(VisionValueChangeEvent(htmlInputElement.value.asValue(), name))
vision.asyncControlEvent(ControlValueChangeEvent(htmlInputElement.value.asValue(), name))
}
htmlInputElement.oninput = {
vision.asyncControlEvent(VisionInputEvent(htmlInputElement.value.asValue(), name))
vision.asyncControlEvent(ControlInputEvent(htmlInputElement.value.asValue(), name))
}
htmlInputElement.subscribeToInput(vision)
@@ -81,11 +81,11 @@ internal val checkboxVisionRenderer: ElementVisionRenderer =
}.also { htmlInputElement ->
htmlInputElement.onchange = {
vision.asyncControlEvent(VisionValueChangeEvent(htmlInputElement.value.asValue(), name))
vision.asyncControlEvent(ControlValueChangeEvent(htmlInputElement.value.asValue(), name))
}
htmlInputElement.oninput = {
vision.asyncControlEvent(VisionInputEvent(htmlInputElement.value.asValue(), name))
vision.asyncControlEvent(ControlInputEvent(htmlInputElement.value.asValue(), name))
}
@@ -103,11 +103,11 @@ internal val textVisionRenderer: ElementVisionRenderer =
}.also { htmlInputElement ->
htmlInputElement.onchange = {
vision.asyncControlEvent(VisionValueChangeEvent(htmlInputElement.value.asValue(), name))
vision.asyncControlEvent(ControlValueChangeEvent(htmlInputElement.value.asValue(), name))
}
htmlInputElement.oninput = {
vision.asyncControlEvent(VisionInputEvent(htmlInputElement.value.asValue(), name))
vision.asyncControlEvent(ControlInputEvent(htmlInputElement.value.asValue(), name))
}
htmlInputElement.subscribeToInput(vision)
@@ -125,13 +125,13 @@ internal val numberVisionRenderer: ElementVisionRenderer =
htmlInputElement.onchange = {
htmlInputElement.value.toDoubleOrNull()?.let {
vision.asyncControlEvent(VisionValueChangeEvent(it.asValue(), name))
vision.asyncControlEvent(ControlValueChangeEvent(it.asValue(), name))
}
}
htmlInputElement.oninput = {
htmlInputElement.value.toDoubleOrNull()?.let {
vision.asyncControlEvent(VisionInputEvent(it.asValue(), name))
vision.asyncControlEvent(ControlInputEvent(it.asValue(), name))
}
}
@@ -154,13 +154,13 @@ internal val rangeVisionRenderer: ElementVisionRenderer =
htmlInputElement.onchange = {
htmlInputElement.value.toDoubleOrNull()?.let {
vision.asyncControlEvent(VisionValueChangeEvent(it.asValue(), name))
vision.asyncControlEvent(ControlValueChangeEvent(it.asValue(), name))
}
}
htmlInputElement.oninput = {
htmlInputElement.value.toDoubleOrNull()?.let {
vision.asyncControlEvent(VisionInputEvent(it.asValue(), name))
vision.asyncControlEvent(ControlInputEvent(it.asValue(), name))
}
}

View File

@@ -2,12 +2,13 @@ package space.kscience.visionforge.meta
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.runTest
import space.kscience.dataforge.context.Global
import space.kscience.dataforge.context.request
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.int
import space.kscience.dataforge.meta.remove
import space.kscience.dataforge.meta.set
import space.kscience.visionforge.*
import kotlin.test.Test
import kotlin.test.assertEquals
@@ -19,7 +20,7 @@ internal class PropertyFlowTest {
@Test
fun testChildrenPropertyFlow() = runTest(timeout = 200.milliseconds) {
val parent = MutableVisionGroup(manager){
val parent = MutableVisionGroup(manager) {
properties {
"test" put 11
@@ -35,9 +36,8 @@ internal class PropertyFlowTest {
val child = parent.getVision("child") as MutableVisionGroup<*>
val changesFlow = child.flowProperty("test", inherited = true).map {
it.int!!
}
val changesFlow = child.flowProperty("test", inherited = true)
// child.inheritedEventFlow().filterIsInstance<VisionPropertyChangedEvent>().onEach { event ->
// println(event)
@@ -48,21 +48,28 @@ internal class PropertyFlowTest {
val collectedValues = ArrayList<Int>(5)
val collectorJob = changesFlow.onEach {
collectedValues.add(it)
collectedValues.add(it.int!!)
}.launchIn(this)
delay(2)
assertEquals(22, child.readProperty("test", true).int)
// assertEquals(1, collectedValues.size)
parent.properties["test1"] = 88 // another property
child.properties.remove("test")
delay(2)
assertEquals(11, child.readProperty("test", true).int)
// assertEquals(2, collectedValues.size)
parent.properties["test"] = 33
delay(2)
assertEquals(33, child.readProperty("test", true).int)
// assertEquals(3, collectedValues.size)
collectorJob.cancel()
assertEquals(listOf(22, 11, 33), collectedValues)

View File

@@ -2,19 +2,21 @@ package space.kscience.visionforge.gdml
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.plus
import space.kscience.dataforge.names.NameToken
import space.kscience.gdml.*
import space.kscience.kmath.geometry.RotationOrder
import space.kscience.visionforge.*
import space.kscience.kmath.geometry.euclidean3d.Float32Vector3D
import space.kscience.kmath.geometry.euclidean3d.RotationOrder
import space.kscience.visionforge.VisionBuilder
import space.kscience.visionforge.html.VisionOutput
import space.kscience.visionforge.setStyle
import space.kscience.visionforge.solid.*
import space.kscience.visionforge.style
import space.kscience.visionforge.useStyle
import kotlin.math.cos
import kotlin.math.sin
private val solidsName = "solids".asName()
private val volumesName = "volumes".asName()
private val solidsName = "solids"
private val volumesName = "volumes"
@Suppress("NOTHING_TO_INLINE")
private inline operator fun Number.times(d: Double) = toDouble() * d
@@ -46,7 +48,7 @@ private class GdmlLoader(val settings: GdmlLoaderOptions) {
}
private fun proxySolid(root: Gdml, group: SolidGroup, solid: GdmlSolid, name: String): SolidReference {
val templateName = solidsName + name
val templateName = Name.of(solidsName, name)
if (templates[templateName] == null) {
solids.addSolid(root, solid, name)
}
@@ -61,9 +63,9 @@ private class GdmlLoader(val settings: GdmlLoaderOptions) {
physVolume: GdmlPhysVolume,
volume: GdmlGroup,
): SolidReference {
val templateName = volumesName + volume.name.asName()
val templateName = Name.of(volumesName, volume.name)
if (templates[templateName] == null) {
templates.setVision(templateName, volume(root, volume))
templates[templateName] = volume(root, volume)
}
val ref = group.ref(templateName, physVolume.name).withPosition(root, physVolume)
referenceStore.getOrPut(templateName) { ArrayList() }.add(ref)
@@ -313,7 +315,7 @@ private class GdmlLoader(val settings: GdmlLoaderOptions) {
when (settings.volumeAction(volume)) {
GdmlLoaderOptions.Action.ADD -> {
val group: SolidGroup = volume(root, volume)
this.setVision(physVolume.name, group.withPosition(root, physVolume))
this.setVision(NameToken(physVolume.name), group.withPosition(root, physVolume))
}
GdmlLoaderOptions.Action.PROTOTYPE -> {
@@ -371,7 +373,7 @@ private class GdmlLoader(val settings: GdmlLoaderOptions) {
rootSolid.useStyle(rootStyle)
rootSolid.prototypes {
templates.items.forEach { (name, item) ->
templates.visions.forEach { (name, item) ->
item.parent = null
setVision(name, item)
}
@@ -387,7 +389,7 @@ private class GdmlLoader(val settings: GdmlLoaderOptions) {
public fun Gdml.toVision(block: GdmlLoaderOptions.() -> Unit = {}): SolidGroup {
val settings = GdmlLoaderOptions().apply(block)
return GdmlLoader(settings).transform(this).also {
it.setVision("light", settings.light)
it.setVision(NameToken("light"), settings.light)
}
}
@@ -397,7 +399,7 @@ public fun Gdml.toVision(block: GdmlLoaderOptions.() -> Unit = {}): SolidGroup {
public fun SolidGroup.gdml(gdml: Gdml, key: String? = null, transformer: GdmlLoaderOptions.() -> Unit = {}) {
val vision = gdml.toVision(transformer)
//println(Visual3DPlugin.json.stringify(VisualGroup3D.serializer(), visual))
setVision(SolidGroup.inferNameFor(key,vision), vision)
setVision(SolidGroup.inferNameFor(key, vision), vision)
}
@VisionBuilder

View File

@@ -4,6 +4,7 @@ import space.kscience.dataforge.context.Global
import space.kscience.dataforge.context.info
import space.kscience.dataforge.context.logger
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.length
import space.kscience.dataforge.names.plus
import space.kscience.visionforge.solid.Solid
@@ -21,8 +22,8 @@ private class VisionCounterTree(
// self count for prototypes
var selfCount = 1
val children: Map<Name, VisionCounterTree> by lazy {
(vision as? SolidGroup)?.items?.mapValues { (key, vision) ->
val children: Map<NameToken, VisionCounterTree> by lazy {
(vision as? SolidGroup)?.visions?.mapValues { (key, vision) ->
if (vision is SolidReference) {
prototypes.getOrPut(vision.prototypeName) {
VisionCounterTree(vision.prototypeName, vision.prototype, prototypes)

View File

@@ -22,7 +22,7 @@ class TestCubes {
fun testCubesDirect() {
val vision: SolidGroup = cubes.toVision()
// println(Solids.encodeToString(vision))
val smallBoxPrototype = vision.getPrototype(Name.parse("solids.smallBox")) as? Box
val smallBoxPrototype = vision.getPrototype("solids.smallBox") as? Box
assertNotNull(smallBoxPrototype)
assertEquals(30.0, smallBoxPrototype.xSize.toDouble())
val smallBoxVision = vision["composite-111.smallBox"]?.prototype as? Box
@@ -54,7 +54,7 @@ class TestCubes {
assertNotNull(this.prototype)
}
if (this is SolidGroup) {
items.forEach {
visions.forEach {
it.value.checkPrototypes()
}
}

View File

@@ -1,10 +1,10 @@
package space.kscience.visionforge.gdml
import org.junit.jupiter.api.Test
import space.kscience.dataforge.names.Name
import space.kscience.gdml.Gdml
import space.kscience.gdml.decodeFromStream
import space.kscience.visionforge.solid.Solids
import space.kscience.visionforge.solid.getPrototype
import kotlin.test.assertNotNull
@Suppress("UNUSED_VARIABLE")
@@ -23,7 +23,7 @@ class TestConvertor {
val stream = javaClass.getResourceAsStream("/gdml/cubes.gdml")!!
val gdml = Gdml.decodeFromStream(stream)
val vision = gdml.toVision()
assertNotNull(vision.getPrototype(Name.parse("solids.box")))
assertNotNull(vision.getPrototype("solids.box"))
println(Solids.encodeToString(vision))
}

View File

@@ -3,11 +3,12 @@ package space.kscience.visionforge.jupyter
import io.ktor.http.URLProtocol
import io.ktor.server.application.install
import io.ktor.server.cio.CIO
import io.ktor.server.engine.ApplicationEngine
import io.ktor.server.engine.EmbeddedServer
import io.ktor.server.engine.embeddedServer
import io.ktor.server.util.url
import io.ktor.server.websocket.WebSockets
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.runBlocking
import kotlinx.html.*
import kotlinx.html.stream.createHTML
import org.jetbrains.kotlinx.jupyter.api.*
@@ -59,12 +60,12 @@ public class VisionForge(
private var counter = 0
private var engine: ApplicationEngine? = null
private var server: EmbeddedServer<*,*>? = null
override val coroutineContext: CoroutineContext get() = context.coroutineContext
public fun isServerRunning(): Boolean = engine != null
public fun isServerRunning(): Boolean = server != null
public fun getProperty(name: String): TypedMeta<*>? = configuration[name] ?: context.properties[name]
@@ -73,7 +74,7 @@ public class VisionForge(
host: String = getProperty("visionforge.host").string ?: "localhost",
port: Int = getProperty("visionforge.port").int ?: VisionRoute.DEFAULT_PORT,
) {
engine?.let {
server?.let {
kernel.displayHtml {
p {
style = "color: red;"
@@ -86,7 +87,7 @@ public class VisionForge(
//val connector: EngineConnectorConfig = EngineConnectorConfig(host, port)
engine = context.embeddedServer(CIO, port, host) {
server = context.embeddedServer(CIO, port, host) {
install(WebSockets)
}.start(false)
@@ -99,10 +100,10 @@ public class VisionForge(
}
internal fun stopServer(kernel: KotlinKernelHost) {
engine?.apply {
server?.apply {
logger.info { "Stopping VisionForge server" }
stop(1000, 2000)
engine = null
server = null
}
kernel.displayHtml {
@@ -136,15 +137,19 @@ public class VisionForge(
val id = "fragment[${fragment.hashCode()}/${Random.nextUInt()}]"
div {
this.id = id
val engine = engine
if (engine != null) {
val server = this@VisionForge.server
if (server != null) {
//if server exist, serve dynamically
//server.serveVisionsFromFragment(consumer, "content-${counter++}", fragment)
val cellRoute = "content-${counter++}"
val cache: MutableMap<Name, VisionDisplay> = mutableMapOf()
val url = engine.environment.connectors.first().let {
val connector = runBlocking {
server.engine.resolvedConnectors().first()
}
val url = connector.let {
url {
protocol = URLProtocol.WS
host = it.host
@@ -153,7 +158,7 @@ public class VisionForge(
}
}
engine.application.serveVisionData(VisionRoute(cellRoute, visionManager), cache)
server.application.serveVisionData(VisionRoute(cellRoute, visionManager), cache)
visionFragment(
visionManager,

View File

@@ -5,7 +5,6 @@ import io.ktor.http.HttpStatusCode
import io.ktor.http.URLProtocol
import io.ktor.http.path
import io.ktor.server.application.Application
import io.ktor.server.application.call
import io.ktor.server.application.install
import io.ktor.server.application.log
import io.ktor.server.engine.ConnectorType
@@ -103,7 +102,7 @@ public fun Application.serveVisionData(
configuration: VisionRoute,
resolveVision: (Name) -> Vision?,
) {
require(WebSockets)
install(WebSockets)
routing {
route(configuration.route) {
//TODO do more precise CORS policy
@@ -176,7 +175,7 @@ public fun Application.visionPage(
connector: EngineConnectorConfig? = null,
visionFragment: HtmlVisionFragment,
) {
require(WebSockets)
install(WebSockets)
val cache: MutableMap<Name, VisionDisplay> = mutableMapOf()
@@ -209,6 +208,7 @@ public fun Application.visionPage(
embedData = configuration.dataMode == VisionRoute.Mode.EMBED,
fetchDataUrl = if (configuration.dataMode != VisionRoute.Mode.EMBED) {
url {
this.protocol = if (schema == ConnectorType.HTTPS) URLProtocol.HTTPS else URLProtocol.HTTP
this.host = host
this.port = port
path(route, "data")
@@ -216,7 +216,7 @@ public fun Application.visionPage(
} else null,
updatesUrl = if (configuration.dataMode == VisionRoute.Mode.UPDATE) {
url {
protocol = if (schema == ConnectorType.HTTPS) URLProtocol.WSS else URLProtocol.WS
this.protocol = if (schema == ConnectorType.HTTPS) URLProtocol.WSS else URLProtocol.WS
this.host = host
this.port = port
path(route, "ws")

View File

@@ -1,27 +1,16 @@
package space.kscience.visionforge.server
import io.ktor.server.application.ApplicationCall
import io.ktor.server.application.Plugin
import io.ktor.server.application.install
import io.ktor.server.application.pluginOrNull
import io.ktor.server.engine.ApplicationEngine
import io.ktor.server.engine.EmbeddedServer
import io.ktor.server.engine.EngineConnectorBuilder
import io.ktor.server.engine.EngineConnectorConfig
import io.ktor.util.pipeline.Pipeline
import java.awt.Desktop
import java.net.URI
public fun <P : Pipeline<*, ApplicationCall>, B : Any, F : Any> P.require(
plugin: Plugin<P, B, F>,
): F = pluginOrNull(plugin) ?: install(plugin)
/**
* Connect to a given Ktor server using browser
*/
public fun ApplicationEngine.openInBrowser() {
val connector = environment.connectors.first()
public suspend fun EmbeddedServer<*,*>.openInBrowser() {
val connector = engine.resolvedConnectors().first()
val host = if (connector.host == "0.0.0.0") "127.0.0.1" else connector.host
val uri = URI("http", null, host, connector.port, null, null, null)
Desktop.getDesktop().browse(uri)
@@ -30,7 +19,7 @@ public fun ApplicationEngine.openInBrowser() {
/**
* Stop the server with default timeouts
*/
public fun ApplicationEngine.close(): Unit = stop(1000, 5000)
public fun EmbeddedServer<*,*>.close(): Unit = stop(1000, 5000)
public fun EngineConnectorConfig(host: String, port: Int): EngineConnectorConfig = EngineConnectorBuilder().apply {

View File

@@ -2,7 +2,7 @@ plugins {
id("space.kscience.gradle.mpp")
}
val kmathVersion = "0.3.1"
val kmathVersion = "0.4.1"
kscience {
jvm()
@@ -14,7 +14,7 @@ kscience {
}
useCoroutines()
dependencies {
api("space.kscience:kmath-geometry:0.3.1")
api("space.kscience:kmath-geometry:$kmathVersion")
api(projects.visionforgeCore)
}
dependencies(jvmTest) {

View File

@@ -9,11 +9,11 @@ import kotlin.properties.ReadOnlyProperty
@VisionBuilder
public class ColorAccessor(
private val provider: MutableValueProvider,
private val provider: MutableMeta,
private val colorKey: Name,
) : MutableValueProvider {
public var value: Value?
get() = provider.getValue(colorKey)
get() = provider[colorKey]?.value
set(value) {
provider.setValue(colorKey, value)
}

View File

@@ -3,7 +3,7 @@ package space.kscience.visionforge.solid
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import space.kscience.dataforge.meta.isEmpty
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.meta.update
import space.kscience.visionforge.MutableVisionContainer
import space.kscience.visionforge.VisionBuilder
@@ -32,13 +32,13 @@ public inline fun MutableVisionContainer<Solid>.composite(
@VisionBuilder builder: SolidGroup.() -> Unit,
): Composite {
val group = SolidGroup().apply(builder)
val children = group.items.values.toList()
val children = group.visions.values.toList()
if (children.size != 2) {
error("Composite requires exactly two children, but found ${children.size}")
}
val res = Composite(type, children[0], children[1])
res.properties[Name.EMPTY] = group.properties
res.properties.update(group.properties)
setVision(SolidGroup.inferNameFor(name, res), res)
return res
@@ -56,7 +56,7 @@ public fun SolidGroup.smartComposite(
val group = SolidGroup().apply(builder)
if (name == null && group.properties.isEmpty()) {
//append directly to group if no properties are defined
group.items.forEach { (_, value) ->
group.visions.forEach { (_, value) ->
value.parent = null
static(value)
}

View File

@@ -2,6 +2,7 @@ package space.kscience.visionforge.solid
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import space.kscience.kmath.geometry.euclidean3d.Float32Vector3D
import space.kscience.visionforge.MutableVisionContainer
import space.kscience.visionforge.VisionBuilder
import kotlin.math.cos

View File

@@ -2,6 +2,7 @@ package space.kscience.visionforge.solid
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import space.kscience.kmath.geometry.euclidean3d.Float32Vector3D
import space.kscience.visionforge.MutableVisionContainer
import space.kscience.visionforge.VisionBuilder
import kotlin.math.PI

View File

@@ -2,7 +2,9 @@ package space.kscience.visionforge.solid
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import space.kscience.dataforge.names.parseAsName
import space.kscience.dataforge.names.NameToken
import space.kscience.kmath.geometry.euclidean3d.Float32Space3D
import space.kscience.kmath.geometry.euclidean3d.Float32Vector3D
import space.kscience.visionforge.MutableVisionContainer
/**
@@ -10,13 +12,15 @@ import space.kscience.visionforge.MutableVisionContainer
*/
@Serializable
@SerialName("solid.convex")
public class Convex(public val points: List<Float32Vector3D>) : SolidBase<Convex>()
public class Convex(
public val points: List<@Serializable(Float32Space3D.VectorSerializer::class) Float32Vector3D>
) : SolidBase<Convex>()
public inline fun MutableVisionContainer<Solid>.convex(
name: String? = null,
action: ConvexBuilder.() -> Unit = {},
): Convex = ConvexBuilder().apply(action).build().also {
setVision(name?.parseAsName() ?: SolidGroup.staticNameFor(it), it)
setVision(name?.let(NameToken::parse) ?: SolidGroup.staticNameFor(it), it)
}
public class ConvexBuilder {

View File

@@ -2,6 +2,7 @@ package space.kscience.visionforge.solid
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import space.kscience.kmath.geometry.euclidean3d.Float32Vector3D
import space.kscience.visionforge.MutableVisionContainer
import space.kscience.visionforge.VisionBuilder
import kotlin.math.abs

View File

@@ -6,6 +6,8 @@ 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.geometry.euclidean2d.Float32Vector2D
import space.kscience.kmath.geometry.euclidean3d.Float32Vector3D
import space.kscience.visionforge.MutableVisionContainer
import space.kscience.visionforge.VisionBuilder

View File

@@ -1,70 +0,0 @@
package space.kscience.visionforge.solid
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import space.kscience.kmath.geometry.GeometrySpace
import space.kscience.kmath.geometry.Vector2D
import space.kscience.kmath.operations.ScaleOperations
import kotlin.math.pow
import kotlin.math.sqrt
@Serializable(Float32Euclidean2DSpace.VectorSerializer::class)
public interface Float32Vector2D: Vector2D<Float>
public object Float32Euclidean2DSpace :
GeometrySpace<Float32Vector2D>,
ScaleOperations<Float32Vector2D> {
@Serializable
@SerialName("Float32Vector2D")
private data class Vector2DImpl(
override val x: Float,
override val y: Float,
) : Float32Vector2D
public object VectorSerializer : KSerializer<Float32Vector2D> {
private val proxySerializer = Vector2DImpl.serializer()
override val descriptor: SerialDescriptor get() = proxySerializer.descriptor
override fun deserialize(decoder: Decoder): Float32Vector2D = decoder.decodeSerializableValue(proxySerializer)
override fun serialize(encoder: Encoder, value: Float32Vector2D) {
val vector = value as? Vector2DImpl ?: Vector2DImpl(value.x, value.y)
encoder.encodeSerializableValue(proxySerializer, vector)
}
}
public fun vector(x: Float, y: Float): Float32Vector2D =
Vector2DImpl(x, y)
public fun vector(x: Number, y: Number): Float32Vector2D =
vector(x.toFloat(), y.toFloat())
override val zero: Float32Vector2D by lazy { vector(0f, 0f) }
override fun norm(arg: Float32Vector2D): Double = sqrt(arg.x.pow(2) + arg.y.pow(2)).toDouble()
public fun Float32Vector2D.norm(): Double = norm(this)
override fun Float32Vector2D.unaryMinus(): Float32Vector2D = vector(-x, -y)
override fun Float32Vector2D.distanceTo(other: Float32Vector2D): Double = (this - other).norm()
override fun add(left: Float32Vector2D, right: Float32Vector2D): Float32Vector2D =
vector(left.x + right.x, left.y + right.y)
override fun scale(a: Float32Vector2D, value: Double): Float32Vector2D =
vector(a.x * value, a.y * value)
override fun Float32Vector2D.dot(other: Float32Vector2D): Double =
(x * other.x + y * other.y).toDouble()
public val xAxis: Float32Vector2D = vector(1.0, 0.0)
public val yAxis: Float32Vector2D = vector(0.0, 1.0)
}
public fun Float32Vector2D(x: Number, y: Number): Float32Vector2D = Float32Euclidean2DSpace.vector(x, y)

View File

@@ -1,113 +0,0 @@
package space.kscience.visionforge.solid
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import space.kscience.kmath.geometry.GeometrySpace
import space.kscience.kmath.geometry.Vector3D
import space.kscience.kmath.operations.ScaleOperations
import kotlin.math.pow
import kotlin.math.sqrt
@Serializable(Float32Euclidean3DSpace.VectorSerializer::class)
public interface Float32Vector3D: Vector3D<Float>
public object Float32Euclidean3DSpace :
GeometrySpace<Float32Vector3D>,
ScaleOperations<Float32Vector3D>{
@Serializable
@SerialName("Float32Vector3D")
private data class Vector3DImpl(
override val x: Float,
override val y: Float,
override val z: Float,
) : Float32Vector3D
public object VectorSerializer : KSerializer<Float32Vector3D> {
private val proxySerializer = Vector3DImpl.serializer()
override val descriptor: SerialDescriptor get() = proxySerializer.descriptor
override fun deserialize(decoder: Decoder): Float32Vector3D = decoder.decodeSerializableValue(proxySerializer)
override fun serialize(encoder: Encoder, value: Float32Vector3D) {
val vector = value as? Vector3DImpl ?: Vector3DImpl(value.x, value.y, value.z)
encoder.encodeSerializableValue(proxySerializer, vector)
}
}
public fun vector(x: Float, y: Float, z: Float): Float32Vector3D =
Vector3DImpl(x, y, z)
public fun vector(x: Number, y: Number, z: Number): Float32Vector3D =
vector(x.toFloat(), y.toFloat(), z.toFloat())
override val zero: Float32Vector3D by lazy { vector(0.0, 0.0, 0.0) }
override fun norm(arg: Float32Vector3D): Double = sqrt(arg.x.pow(2) + arg.y.pow(2) + arg.z.pow(2)).toDouble()
public fun Float32Vector3D.norm(): Double = norm(this)
override fun Float32Vector3D.unaryMinus(): Float32Vector3D = vector(-x, -y, -z)
override fun Float32Vector3D.distanceTo(other: Float32Vector3D): Double = (this - other).norm()
override fun add(left: Float32Vector3D, right: Float32Vector3D): Float32Vector3D =
vector(left.x + right.x, left.y + right.y, left.z + right.z)
override fun scale(a: Float32Vector3D, value: Double): Float32Vector3D =
vector(a.x * value, a.y * value, a.z * value)
override fun Float32Vector3D.dot(other: Float32Vector3D): Double =
(x * other.x + y * other.y + z * other.z).toDouble()
private fun leviCivita(i: Int, j: Int, k: Int): Int = when {
// even permutation
i == 0 && j == 1 && k == 2 -> 1
i == 1 && j == 2 && k == 0 -> 1
i == 2 && j == 0 && k == 1 -> 1
// odd permutations
i == 2 && j == 1 && k == 0 -> -1
i == 0 && j == 2 && k == 1 -> -1
i == 1 && j == 0 && k == 2 -> -1
else -> 0
}
/**
* Compute vector product of [first] and [second]. The basis is assumed to be right-handed.
*/
public fun vectorProduct(
first: Float32Vector3D,
second: Float32Vector3D,
): Float32Vector3D {
var x = 0.0
var y = 0.0
var z = 0.0
for (j in (0..2)) {
for (k in (0..2)) {
x += leviCivita(0, j, k) * first[j] * second[k]
y += leviCivita(1, j, k) * first[j] * second[k]
z += leviCivita(2, j, k) * first[j] * second[k]
}
}
return vector(x, y, z)
}
/**
* Vector product in a right-handed basis
*/
public infix fun Float32Vector3D.cross(other: Float32Vector3D): Float32Vector3D = vectorProduct(this, other)
public val xAxis: Float32Vector3D = vector(1.0, 0.0, 0.0)
public val yAxis: Float32Vector3D = vector(0.0, 1.0, 0.0)
public val zAxis: Float32Vector3D = vector(0.0, 0.0, 1.0)
}
public fun Float32Vector3D(x: Number, y: Number, z: Number): Float32Vector3D = Float32Euclidean3DSpace.vector(x,y,z)

View File

@@ -1,6 +1,7 @@
package space.kscience.visionforge.solid
import space.kscience.dataforge.meta.Meta
import space.kscience.kmath.geometry.euclidean3d.Float32Vector3D
/**
* @param T the type of resulting geometry

View File

@@ -2,6 +2,7 @@ package space.kscience.visionforge.solid
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import space.kscience.kmath.geometry.euclidean3d.Float32Vector3D
import space.kscience.visionforge.MutableVisionContainer
import space.kscience.visionforge.VisionBuilder

View File

@@ -9,6 +9,7 @@ import space.kscience.dataforge.meta.descriptors.value
import space.kscience.dataforge.meta.number
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.kmath.geometry.euclidean3d.Float32Vector3D
import space.kscience.visionforge.*
@Serializable

View File

@@ -3,7 +3,8 @@ package space.kscience.visionforge.solid
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import space.kscience.dataforge.meta.number
import space.kscience.dataforge.names.parseAsName
import space.kscience.dataforge.names.NameToken
import space.kscience.kmath.geometry.euclidean3d.Float32Vector3D
import space.kscience.visionforge.MutableVisionContainer
import space.kscience.visionforge.VisionBuilder
@@ -25,5 +26,5 @@ public fun MutableVisionContainer<Solid>.polyline(
name: String? = null,
action: PolyLine.() -> Unit = {},
): PolyLine = PolyLine(points.toList()).apply(action).also {
setVision(name?.parseAsName() ?: SolidGroup.staticNameFor(it), it)
setVision(name?.let(NameToken::parse) ?: SolidGroup.staticNameFor(it), it)
}

View File

@@ -1,6 +1,7 @@
package space.kscience.visionforge.solid
import kotlinx.serialization.Serializable
import space.kscience.kmath.geometry.euclidean2d.Float32Vector2D
import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.sin

View File

@@ -9,8 +9,10 @@ import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
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.kmath.complex.QuaternionAlgebra
import space.kscience.kmath.geometry.Angle
import space.kscience.kmath.geometry.euclidean3d.*
import space.kscience.kmath.geometry.radians
import space.kscience.visionforge.MutableVision
import space.kscience.visionforge.Vision
import space.kscience.visionforge.Vision.Companion.VISIBLE_KEY
@@ -259,6 +261,6 @@ public var Solid.scaleZ: Number by float32(Z_SCALE_KEY, 1f)
/**
* Add rotation with given [angle] relative to given [axis]
*/
public fun Solid.rotate(angle: Angle, axis: DoubleVector3D): Unit = with(QuaternionField) {
public fun Solid.rotate(angle: Angle, axis: Float64Vector3D): Unit = with(QuaternionAlgebra) {
quaternion = Quaternion.fromRotation(angle, axis) * quaternion
}

View File

@@ -3,19 +3,8 @@ package space.kscience.visionforge.solid
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.parseAsName
import space.kscience.dataforge.provider.Path
import space.kscience.dataforge.provider.length
import space.kscience.dataforge.provider.provide
import space.kscience.dataforge.provider.tail
import space.kscience.dataforge.names.*
import space.kscience.visionforge.*
import kotlin.collections.LinkedHashMap
import kotlin.collections.Map
import kotlin.collections.first
import kotlin.collections.set
/**
@@ -34,6 +23,8 @@ public interface PrototypeHolder {
public fun getPrototype(name: Name): Solid?
}
public fun PrototypeHolder.getPrototype(name: String): Solid? = getPrototype(Name.parse(name))
public interface SolidContainer : VisionGroup<Solid>, Solid {
override suspend fun receiveEvent(event: VisionEvent) {
super<VisionGroup>.receiveEvent(event)
@@ -47,9 +38,9 @@ public interface SolidContainer : VisionGroup<Solid>, Solid {
@SerialName("group.solid")
public class SolidGroup : AbstractVision(), SolidContainer, PrototypeHolder, MutableVisionGroup<Solid> {
private val _solids = LinkedHashMap<Name, Solid>()
private val _solids = LinkedHashMap<NameToken, Solid>()
override val items: Map<Name, Solid> get() = _solids
override val visions: Map<NameToken, Solid> get() = _solids
private var prototypes: SolidGroup? = null
@@ -65,8 +56,7 @@ public class SolidGroup : AbstractVision(), SolidContainer, PrototypeHolder, Mut
* Get a prototype redirecting the request to the parent if prototype is not found.
* If a prototype is a ref, then it is unfolded automatically.
*/
override fun getPrototype(name: Name): Solid? =
prototypes?.getVision(name)?.prototype ?: (parent as? PrototypeHolder)?.getPrototype(name)
override fun getPrototype(name: Name): Solid? = prototypes?.getVision(name)?.prototype ?: (parent as? PrototypeHolder)?.getPrototype(name)
/**
* Create or edit prototype node as a group
@@ -74,63 +64,69 @@ public class SolidGroup : AbstractVision(), SolidContainer, PrototypeHolder, Mut
override fun prototypes(builder: SolidGroup.() -> Unit): Unit {
(prototypes ?: SolidGroup().also {
prototypes = it
parent = this
it.parent = this
}).apply(builder)
}
override val defaultChainTarget: String get() = VisionGroup.VISION_CHILD_TARGET
override fun setVision(name: Name, vision: Solid?) {
override fun setVision(token: NameToken, vision: Solid?) {
if (vision == null) {
_solids.remove(name)?.let {
_solids.remove(token)?.let {
it.parent = null
}
} else {
_solids[name] = vision
_solids[token] = vision
vision.parent = this
}
emitEvent(VisionGroupCompositionChangedEvent(this, name))
emitEvent(VisionGroupCompositionChangedEvent(this, token))
}
public companion object {
public const val STATIC_TOKEN_BODY: String = "@static"
public fun staticNameFor(vision: Vision): Name =
NameToken(STATIC_TOKEN_BODY, vision.hashCode().toString(16)).asName()
public fun staticNameFor(vision: Vision): NameToken =
NameToken(STATIC_TOKEN_BODY, vision.hashCode().toString(16))
public fun inferNameFor(name: String?, vision: Vision): Name = name?.parseAsName() ?: staticNameFor(vision)
public fun inferNameFor(name: String?, vision: Vision): NameToken =
name?.let(NameToken::parse) ?: staticNameFor(vision)
}
}
public operator fun SolidContainer.get(name: Name): Solid? = getVision(name)
public fun SolidContainer.getVision(name: Name): Solid? = when (name.length) {
0 -> this
1 -> getVision(name.first())
else -> (getVision(name.first()) as? SolidContainer)?.getVision(name.cutFirst())
}
public operator fun SolidContainer.get(name: String): Solid? = get(name.parseAsName())
public fun SolidContainer.getVision(name: String): Solid? = getVision(name.parseAsName())
public operator fun SolidContainer.get(path: Path): Solid? =
provide(path, VisionGroup.VISION_CHILD_TARGET) as? Solid
public operator fun SolidGroup.get(name: NameToken): Solid? = getVision(name)
public operator fun SolidGroup.set(path: Path, vision: Solid?) {
when (path.length) {
public operator fun SolidGroup.get(name: Name): Solid? = getVision(name)
public operator fun SolidGroup.get(name: String): Solid? = getVision(name)
public operator fun SolidGroup.set(name: NameToken, value: Solid?) = setVision(name, value)
public operator fun SolidGroup.set(name: Name, vision: Solid?) {
when (name.length) {
0 -> error("Can't set vision with empty path")
1 -> {
val token = path.first()
check (token.target != null && token.target != VisionGroup.VISION_CHILD_TARGET) {
"Unsupported target: ${token.target}"
}
setVision(token.name, vision)
val token = name.first()
setVision(token, vision)
}
else -> {
val token = path.first()
check (token.target != null && token.target != VisionGroup.VISION_CHILD_TARGET) {
"Unsupported target: ${token.target}"
}
when (val existing = getVision(token.name)) {
val token = name.first()
when (val existing = getVision(token)) {
null -> SolidGroup().also { newGroup ->
setVision(token.name, newGroup)
newGroup[path.tail!!] = vision
setVision(token, newGroup)
newGroup[name.cutFirst()] = vision
}
is SolidGroup -> existing[path.tail!!] = vision
is SolidGroup -> existing[name.cutFirst()] = vision
else -> error("Can't set Solid by path because of existing non-group Solid at $token")
}
@@ -147,21 +143,21 @@ public fun MutableVisionContainer<Solid>.static(solid: Solid) {
@VisionBuilder
public inline fun MutableVisionContainer<Solid>.solidGroup(
name: Name? = null,
token: NameToken? = null,
builder: SolidGroup.() -> Unit = {},
): SolidGroup = SolidGroup().also {
setVision(name ?: SolidGroup.staticNameFor(it), it)
setVision(token ?: SolidGroup.staticNameFor(it), it)
}.apply(builder)
//root first, update later
/**
* Define a group with given [name], attach it to this parent and return it.
* Define a group with given [token], attach it to this parent and return it.
*/
@VisionBuilder
public inline fun MutableVisionContainer<Solid>.solidGroup(
name: String,
token: String,
action: SolidGroup.() -> Unit = {},
): SolidGroup = solidGroup(name.parseAsName(), action)
): SolidGroup = solidGroup(NameToken.parse(token), action)
/**
* Create a [SolidGroup] using given configuration [block]

View File

@@ -4,15 +4,21 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.map
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import space.kscience.dataforge.meta.Laminate
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MutableMeta
import space.kscience.dataforge.meta.MutableMetaProxy
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.names.*
import space.kscience.dataforge.provider.Path
import space.kscience.visionforge.*
import space.kscience.visionforge.solid.SolidReference.Companion.REFERENCE_CHILD_PROPERTY_PREFIX
@@ -30,6 +36,20 @@ public val Vision.prototype: Solid
else -> error("This Vision is neither Solid nor SolidReference")
}
public object PathSerializer : KSerializer<Path> {
override val descriptor: SerialDescriptor
get() = String.serializer().descriptor
override fun serialize(encoder: Encoder, value: Path) {
encoder.encodeString(value.toString())
}
override fun deserialize(decoder: Decoder): Path {
return Path.parse(decoder.decodeString())
}
}
@Serializable
@SerialName("solid.ref")
public class SolidReference(
@@ -52,13 +72,13 @@ public class SolidReference(
override val descriptor: MetaDescriptor get() = prototype.descriptor
override val items: Map<Name, Solid>
get() = (prototype as? SolidContainer)?.items?.mapValues { (key, _) ->
SolidReferenceChild(this@SolidReference, this@SolidReference, key)
override val visions: Map<NameToken, Solid>
get() = (prototype as? SolidContainer)?.visions?.mapValues { (key, _) ->
SolidReferenceChild(this@SolidReference, this@SolidReference, key.asName())
} ?: emptyMap()
override fun getVision(name: Name): Solid? = (prototype as? SolidContainer)?.getVision(name)
override fun getVision(token: NameToken): Solid? = (prototype as? SolidContainer)?.getVision(token)
override fun readProperty(
@@ -235,8 +255,8 @@ private class SolidReferenceChild(
return if (listOfMeta.all { it == null }) null else Laminate(listOfMeta)
}
override val items: Map<Name, Solid>
get() = (prototype as? SolidContainer)?.items?.mapValues { (key, _) ->
override val visions: Map<NameToken, Solid>
get() = (prototype as? SolidContainer)?.visions?.mapValues { (key, _) ->
SolidReferenceChild(owner, this, childName + key)
} ?: emptyMap()
@@ -284,15 +304,15 @@ private class SolidReferenceChild(
*/
public fun MutableVisionContainer<Solid>.ref(
templateName: Name,
name: Name? = null,
token: NameToken? = null,
): SolidReference = SolidReference(templateName).also {
setVision(name ?: SolidGroup.staticNameFor(it), it)
setVision(token ?: SolidGroup.staticNameFor(it), it)
}
public fun MutableVisionContainer<Solid>.ref(
templateName: Name,
name: String,
): SolidReference = ref(templateName, name.parseAsName())
): SolidReference = ref(templateName, NameToken.parse(name))
/**
* Add new [SolidReference] wrapping given object and automatically adding it to the prototypes.
@@ -306,10 +326,10 @@ public fun SolidGroup.newRef(
val existing = prototypeHolder.getPrototype(prototypeName)
if (existing == null) {
prototypeHolder.prototypes {
setVision(prototypeName, obj)
set(prototypeName, obj)
}
} else if (existing != obj) {
error("Can't add different prototype on top of existing one")
}
return ref(prototypeName, name?.parseAsName())
return ref(prototypeName, name?.let { NameToken.parse(it) })
}

View File

@@ -11,7 +11,7 @@ import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.PluginFactory
import space.kscience.dataforge.context.PluginTag
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
import space.kscience.visionforge.*
import space.kscience.visionforge.html.VisionOutput
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
@@ -22,7 +22,7 @@ public class Solids(meta: Meta) : VisionPlugin(meta), MutableVisionContainer<Sol
override val visionSerializersModule: SerializersModule get() = serializersModuleForSolids
override fun setVision(name: Name, vision: Solid?) {
override fun setVision(token: NameToken, vision: Solid?) {
vision?.setAsRoot(visionManager)
}
@@ -91,7 +91,7 @@ public inline fun VisionOutput.solid(options: Canvas3DOptions? = null, block: So
meta = options.meta
}
return SolidGroup().apply(block).apply {
if (items.values.none { it is LightSource }) {
if (visions.values.none { it is LightSource }) {
ambientLight()
}
}

View File

@@ -2,7 +2,8 @@ package space.kscience.visionforge.solid
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import space.kscience.dataforge.names.parseAsName
import space.kscience.dataforge.names.NameToken
import space.kscience.kmath.geometry.euclidean3d.Float32Vector3D
import space.kscience.visionforge.MutableVisionContainer
import space.kscience.visionforge.VisionBuilder
import kotlin.math.PI
@@ -59,5 +60,5 @@ public inline fun MutableVisionContainer<Solid>.sphere(
): Sphere = Sphere(
radius.toFloat(),
).apply(action).also {
setVision(name?.parseAsName() ?: SolidGroup.staticNameFor(it), it)
setVision(name?.let(NameToken::parse) ?: SolidGroup.staticNameFor(it), it)
}

View File

@@ -2,7 +2,8 @@ package space.kscience.visionforge.solid
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import space.kscience.dataforge.names.parseAsName
import space.kscience.dataforge.names.NameToken
import space.kscience.kmath.geometry.euclidean3d.Float32Vector3D
import space.kscience.visionforge.MutableVisionContainer
import space.kscience.visionforge.VisionBuilder
import kotlin.math.PI
@@ -86,5 +87,5 @@ public inline fun MutableVisionContainer<Solid>.sphereLayer(
thetaStart.toFloat(),
theta.toFloat()
).apply(action).also {
setVision(name?.parseAsName() ?: SolidGroup.staticNameFor(it), it)
setVision(name?.let(NameToken::parse) ?: SolidGroup.staticNameFor(it), it)
}

View File

@@ -6,6 +6,7 @@ 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.geometry.euclidean3d.Float32Vector3D
import space.kscience.kmath.structures.Float32
import space.kscience.visionforge.MutableVisionContainer
import space.kscience.visionforge.VisionBuilder

View File

@@ -4,6 +4,9 @@ import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaProvider
import space.kscience.dataforge.meta.float
import space.kscience.dataforge.meta.get
import space.kscience.kmath.geometry.euclidean2d.Float32Vector2D
import space.kscience.kmath.geometry.euclidean3d.Float32Space3D
import space.kscience.kmath.geometry.euclidean3d.Float32Vector3D
import space.kscience.visionforge.solid.Solid.Companion.X_KEY
import space.kscience.visionforge.solid.Solid.Companion.Y_KEY
import space.kscience.visionforge.solid.Solid.Companion.Z_KEY
@@ -35,7 +38,7 @@ internal fun Meta.toVector2D(): Float32Vector2D =
// z /= norm
//}
internal fun MetaProvider.point3D(default: Float = 0f) = Float32Euclidean3DSpace.vector(
internal fun MetaProvider.point3D(default: Float = 0f) = Float32Space3D.vector(
get(X_KEY).float ?: default,
get(Y_KEY).float ?: default,
get(Z_KEY).float ?: default

View File

@@ -2,7 +2,8 @@ package space.kscience.visionforge.solid.transform
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name
import space.kscience.kmath.complex.QuaternionField
import space.kscience.kmath.complex.QuaternionAlgebra
import space.kscience.visionforge.solid.*
private operator fun Number.plus(other: Number) = toFloat() + other.toFloat()
@@ -13,7 +14,7 @@ internal fun Solid.updateFrom(other: Solid): Solid {
x += other.x
y += other.y
z += other.y
quaternion = with(QuaternionField) { other.quaternion * quaternion }
quaternion = with(QuaternionAlgebra) { other.quaternion * quaternion }
scaleX *= other.scaleX
scaleY *= other.scaleY
scaleZ *= other.scaleZ
@@ -27,13 +28,13 @@ internal object RemoveSingleChild : VisualTreeTransform<SolidGroup>() {
override fun SolidGroup.transformInPlace() {
fun SolidGroup.replaceChildren() {
items.forEach { (childName, parent) ->
visions.forEach { (childName, parent) ->
if (parent is SolidReference) return@forEach //ignore refs
if (parent is SolidGroup) {
parent.replaceChildren()
}
if (parent is SolidGroup && parent.items.size == 1) {
val child: Solid = parent.items.values.first()
if (parent is SolidGroup && parent.visions.size == 1) {
val child: Solid = parent.visions.values.first()
val newParent = child.updateFrom(parent)
newParent.parent = null
setVision(childName, newParent)

View File

@@ -4,14 +4,12 @@ import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name
import space.kscience.visionforge.solid.SolidGroup
import space.kscience.visionforge.solid.SolidReference
import kotlin.collections.component1
import kotlin.collections.component2
import kotlin.collections.set
import space.kscience.visionforge.solid.set
@DFExperimental
internal object UnRef : VisualTreeTransform<SolidGroup>() {
private fun SolidGroup.countRefs(): Map<Name, Int> {
return items.values.fold(HashMap()) { reducer, vision ->
return visions.values.fold(HashMap()) { reducer, vision ->
if (vision is SolidGroup) {
val counter = vision.countRefs()
counter.forEach { (key, value) ->
@@ -27,16 +25,16 @@ internal object UnRef : VisualTreeTransform<SolidGroup>() {
private fun SolidGroup.unref(name: Name) {
prototypes {
setVision(name, null)
set(name, null)
}
items.filter { (it.value as? SolidReference)?.prototypeName == name }.forEach { (key, value) ->
visions.filter { (it.value as? SolidReference)?.prototypeName == name }.forEach { (key, value) ->
val reference = value as SolidReference
val newChild = reference.prototype.updateFrom(reference)
newChild.parent = null
setVision(key, newChild) // replace proxy with merged object
}
items.values.filterIsInstance<SolidGroup>().forEach { it.unref(name) }
visions.values.filterIsInstance<SolidGroup>().forEach { it.unref(name) }
}
override fun SolidGroup.transformInPlace() {

View File

@@ -21,7 +21,7 @@ class ConvexTest {
}
}
val convex = group.items.values.first() as Convex
val convex = group.visions.values.first() as Convex
val json = Solids.jsonForSolids.encodeToJsonElement(Convex.serializer(), convex)
val meta = json.toMeta()

View File

@@ -43,7 +43,7 @@ class GroupTest {
}
}
assertEquals(3, group.items.count())
assertEquals(3, group.visions.count())
assertEquals(300.0, group["intersect"]?.y?.toDouble())
assertEquals(-300.0, group["subtract"]?.y?.toDouble())
}

View File

@@ -6,6 +6,7 @@ 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.readProperty
import space.kscience.visionforge.styles
import space.kscience.visionforge.updateStyle
import space.kscience.visionforge.useStyle
@@ -62,7 +63,7 @@ class SolidPropertyTest {
}
}
}
assertEquals(22, box?.properties?.getValue("test")?.int)
assertEquals(22, box?.readProperty("test")?.int)
}
@Test

View File

@@ -30,7 +30,7 @@ class SolidReferenceTest {
fun testReferenceSerialization(){
val serialized = Solids.jsonForSolids.encodeToJsonElement(groupWithReference)
val deserialized = Solids.jsonForSolids.decodeFromJsonElement(SolidGroup.serializer(), serialized)
assertEquals(groupWithReference.items["test"]?.color?.string, deserialized.items["test"]?.color?.string)
assertEquals(groupWithReference.visions["test"]?.color?.string, deserialized.visions["test"]?.color?.string)
assertEquals("blue", (deserialized.get("test") as Solid).color.string)
}
}

View File

@@ -4,9 +4,9 @@ import kotlinx.coroutines.test.runTest
import space.kscience.dataforge.context.Global
import space.kscience.dataforge.context.request
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.asValue
import space.kscience.dataforge.names.asName
import space.kscience.visionforge.VisionChange
import space.kscience.visionforge.getOrCreateChange
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
@@ -25,15 +25,22 @@ internal class VisionUpdateTest {
color(123)
box(100, 100, 100)
}
propertyChanged("top".asName(), SolidMaterial.MATERIAL_COLOR_KEY, Meta("red".asValue()))
propertyChanged("origin".asName(), SolidMaterial.MATERIAL_COLOR_KEY, Meta("red".asValue()))
getOrCreateChange("top".asName()).propertyChanged(
SolidMaterial.MATERIAL_COLOR_KEY,
Meta("red")
)
getOrCreateChange("origin".asName()).propertyChanged(
SolidMaterial.MATERIAL_COLOR_KEY,
Meta("red")
)
}
targetVision.receiveEvent(dif)
assertTrue { targetVision.get("top") is SolidGroup }
assertEquals("red", (targetVision.get("origin") as Solid).color.string) // Should work
assertTrue { targetVision["top"] is SolidGroup }
assertEquals("red", (targetVision["origin"] as Solid).color.string) // Should work
assertEquals(
"#00007b",
(targetVision.get("top") as Solid).color.string
(targetVision["top"] as Solid).color.string
) // new item always takes precedence
}
@@ -44,8 +51,14 @@ internal class VisionUpdateTest {
color(123)
box(100, 100, 100)
}
propertyChanged("top".asName(), SolidMaterial.MATERIAL_COLOR_KEY, Meta("red".asValue()))
propertyChanged("origin".asName(), SolidMaterial.MATERIAL_COLOR_KEY, Meta("red".asValue()))
getOrCreateChange("top".asName()).propertyChanged(
SolidMaterial.MATERIAL_COLOR_KEY,
Meta("red")
)
getOrCreateChange("origin".asName()).propertyChanged(
SolidMaterial.MATERIAL_COLOR_KEY,
Meta("red")
)
}
val serialized = visionManager.jsonFormat.encodeToString(VisionChange.serializer(), change)
println(serialized)

View File

@@ -1,5 +1,6 @@
package space.kscience.visionforge.tables
import js.import.importAsync
import js.objects.jso
import org.w3c.dom.Element
import org.w3c.dom.HTMLElement
@@ -25,8 +26,8 @@ public class TableVisionJsPlugin : AbstractPlugin(), ElementVisionRenderer {
override fun attach(context: Context) {
super.attach(context)
kotlinext.js.require<Any>("tabulator-tables/dist/css/tabulator.min.css")
kotlinext.js.require<Any>("tabulator-tables/src/js/modules/ResizeColumns/ResizeColumns.js")
importAsync<Any>("tabulator-tables/dist/css/tabulator.min.css")
importAsync<Any>("tabulator-tables/src/js/modules/ResizeColumns/ResizeColumns.js")
}
override fun rateVision(vision: Vision): Int = when (vision) {
@@ -81,7 +82,7 @@ public class TableVisionJsPlugin : AbstractPlugin(), ElementVisionRenderer {
TabulatorFull(element as HTMLElement, tableOptions)
}
override fun toString(): String = "Table"
override fun toString(): String = "Table"
override fun content(target: String): Map<Name, Any> = when (target) {
ElementVisionRenderer.TYPE -> mapOf("table".asName() to this)

View File

@@ -19,6 +19,10 @@ kscience {
jsMain {
api(projects.visionforgeComposeHtml)
api("org.jetbrains.kotlin-wrappers:kotlin-js")
api("io.github.vinceglb:filekit-core:0.8.8")
// api(npm("file-saver", "2.0.5"))
// api(npm("@types/file-saver", "2.0.7"))
implementation(npm("three", "0.143.0"))
implementation(npm("three-csg-ts", "3.1.13"))
implementation(npm("three.meshline", "1.4.0"))

View File

@@ -1,8 +1,8 @@
package space.kscience.visionforge.solid.three
import space.kscience.dataforge.meta.Meta
import space.kscience.visionforge.solid.Float32Euclidean3DSpace
import space.kscience.visionforge.solid.Float32Vector3D
import space.kscience.kmath.geometry.euclidean3d.Float32Space3D
import space.kscience.kmath.geometry.euclidean3d.Float32Vector3D
import space.kscience.visionforge.solid.GeometryBuilder
import three.core.BufferGeometry
import three.core.Float32BufferAttribute
@@ -44,7 +44,7 @@ public class ThreeGeometryBuilder : GeometryBuilder<BufferGeometry> {
vertex3: Float32Vector3D,
normal: Float32Vector3D?,
meta: Meta,
) = with(Float32Euclidean3DSpace) {
) = with(Float32Space3D) {
val actualNormal: Float32Vector3D = normal ?: ((vertex3 - vertex2) cross (vertex1 - vertex2))
indices.add(
vertex(vertex1, actualNormal),

View File

@@ -17,7 +17,6 @@ import space.kscience.visionforge.solid.*
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
import space.kscience.visionforge.solid.three.compose.ThreeView
import three.core.Object3D
import kotlin.collections.set
import kotlin.reflect.KClass
import three.objects.Group as ThreeGroup
@@ -67,12 +66,12 @@ public class ThreePlugin : AbstractPlugin(), ComposeHtmlVisionRenderer {
is SolidReference -> ThreeReferenceFactory.build(this, vision, observe)
is SolidGroup -> {
val group = ThreeGroup()
vision.items.forEach { (name, child) ->
vision.visions.forEach { (name, child) ->
if (child.ignore != true) {
try {
val object3D = buildObject3D(
child,
if (name.first().body == SolidGroup.STATIC_TOKEN_BODY) false else observe
if (name.body == SolidGroup.STATIC_TOKEN_BODY) false else observe
)
// disable tracking changes for statics
group[name] = object3D
@@ -103,7 +102,6 @@ public class ThreePlugin : AbstractPlugin(), ComposeHtmlVisionRenderer {
}
is VisionGroupCompositionChangedEvent -> {
val childName = event.childName
if (childName.isEmpty()) return@onEach
val child = vision.get(childName)
@@ -260,10 +258,13 @@ internal operator fun Object3D.set(name: Name, obj: Object3D) {
}
}
internal fun Object3D.findChild(token: NameToken): Object3D? =
children.find { it.name == token.toString() }
internal fun Object3D.findChild(name: Name): Object3D? {
return when {
name.isEmpty() -> this
name.length == 1 -> this.children.find { it.name == name.tokens.first().toString() }
else -> findChild(name.tokens.first().asName())?.findChild(name.cutFirst())
name.length == 1 -> findChild(name.tokens.first())
else -> findChild(name.tokens.first())?.findChild(name.cutFirst())
}
}

View File

@@ -7,7 +7,7 @@ import space.kscience.visionforge.onPropertyChange
import space.kscience.visionforge.solid.Solid
import space.kscience.visionforge.solid.SolidReference
import space.kscience.visionforge.solid.SolidReference.Companion.REFERENCE_CHILD_PROPERTY_PREFIX
import space.kscience.visionforge.solid.get
import space.kscience.visionforge.solid.getVision
import three.core.Object3D
import three.objects.Mesh
import kotlin.reflect.KClass
@@ -54,7 +54,7 @@ public object ThreeReferenceFactory : ThreeFactory<SolidReference> {
val childName = name.firstOrNull()?.index?.let(Name::parse)
?: error("Wrong syntax for reference child property: '$name'")
val propertyName = name.cutFirst()
val referenceChild = vision[childName]
val referenceChild = vision.getVision(childName)
?: error("Reference child with name '$childName' not found")
val child = object3D.findChild(childName) ?: error("Object child with name '$childName' not found")
child.updateProperty(referenceChild, propertyName)

View File

@@ -4,15 +4,18 @@ import androidx.compose.runtime.Composable
import bootstrap.Button
import bootstrap.Color
import bootstrap.Column
import io.github.vinceglb.filekit.core.FileKit
import kotlinx.coroutines.launch
import org.jetbrains.compose.web.dom.Hr
import org.w3c.files.Blob
import org.w3c.files.BlobPropertyBag
import space.kscience.dataforge.context.Global
import space.kscience.dataforge.names.Name
import space.kscience.visionforge.Vision
import space.kscience.visionforge.encodeToString
import space.kscience.visionforge.html.*
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
//private val fileSaver = importAsync<dynamic>("file-saver")
@Composable
internal fun CanvasControls(
vision: Vision?,
@@ -23,9 +26,19 @@ internal fun CanvasControls(
Button("Export", color = Color.Info, styling = { Layout.width = bootstrap.Layout.Width.Full }) {
val json = vision.encodeToString()
val fileSaver = kotlinext.js.require<dynamic>("file-saver")
val blob = Blob(arrayOf(json), BlobPropertyBag("text/json;charset=utf-8"))
fileSaver.saveAs(blob, "${options.canvasName}.json") as Unit
Global.launch {
FileKit.saveFile(
baseName = options.canvasName,
extension = "json",
bytes = json.encodeToByteArray()
)
}
// val blob = Blob(arrayOf(json), BlobPropertyBag("text/json;charset=utf-8"))
//
// fileSaver.then {
// it.saveAs(blob, "${options.canvasName}.json") as Unit
// }
}
}
Hr()

View File

@@ -33,8 +33,6 @@ kscience {
jsMain {
api(projects.visionforgeThreejs)
api(npm("file-saver", "2.0.5"))
api(npm("@types/file-saver", "2.0.7"))
compileOnly(npm("webpack-bundle-analyzer", "4.5.0"))
}
}