[WIP] simplification of API
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
----------------------------------------------------------------------------
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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")
|
||||
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -25,7 +25,7 @@ import kotlin.random.Random
|
||||
|
||||
|
||||
@Suppress("ExtractKtorModule")
|
||||
fun main() {
|
||||
suspend fun main() {
|
||||
val satContext = Context("sat") {
|
||||
plugin(Solids)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -5,7 +5,7 @@ plugins{
|
||||
|
||||
allprojects {
|
||||
group = "space.kscience"
|
||||
version = "0.7.2"
|
||||
version = "0.8.0"
|
||||
}
|
||||
|
||||
readme {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()) }
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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 ?: ""
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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]
|
||||
|
||||
@@ -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) })
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user