diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8c73f7d9..4b57e1c0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,15 +8,15 @@ on: jobs: build: runs-on: ubuntu-latest - timeout-minutes: 40 + timeout-minutes: 30 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3.5.3 - name: Set up JDK 11 - uses: actions/setup-java@v2.5.0 + uses: actions/setup-java@v3.12.0 with: java-version: 11 distribution: liberica - name: execute build - uses: gradle/gradle-build-action@v2 + uses: gradle/gradle-build-action@v2.7.1 with: arguments: build diff --git a/CHANGELOG.md b/CHANGELOG.md index afc740a3..dbb44a80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - MeshLine for thick lines ### Changed +- Color accessor property is now `colorProperty`. Color uses `invoke` instead of `set` - API update for server and pages - Edges moved to solids module for easier construction - Visions **must** be rooted in order to subscribe to updates. @@ -20,6 +21,7 @@ ### Removed ### Fixed +- Jupyter integration for IDEA and Jupyter lab. ### Security diff --git a/build.gradle.kts b/build.gradle.kts index 30eea98f..68c3569b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,7 +13,7 @@ val fxVersion by extra("11") allprojects { group = "space.kscience" - version = "0.3.0-dev-9" + version = "0.3.0-dev-13" } subprojects { diff --git a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/dRootToSolid.kt b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/dRootToSolid.kt index 8015aea6..ef72de52 100644 --- a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/dRootToSolid.kt +++ b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/dRootToSolid.kt @@ -4,12 +4,18 @@ import space.kscience.dataforge.meta.* import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.parseAsName 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.linear.VirtualMatrix import space.kscience.visionforge.MutableVisionContainer import space.kscience.visionforge.isEmpty import space.kscience.visionforge.set import space.kscience.visionforge.solid.* import space.kscience.visionforge.solid.SolidMaterial.Companion.MATERIAL_COLOR_KEY -import kotlin.math.* +import kotlin.math.PI +import kotlin.math.cos +import kotlin.math.sin private val volumesName = Name.EMPTY //"volumes".asName() @@ -27,17 +33,15 @@ private data class RootToSolidContext( val colorCache: MutableMap = mutableMapOf(), ) -// converting to XYZ to Tait–Bryan angles according to https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix +// apply rotation from a matrix private fun Solid.rotate(rot: DoubleArray) { - val xAngle = atan2(-rot[5], rot[8]) - val yAngle = atan2(rot[2], sqrt(1.0 - rot[2].pow(2))) - val zAngle = atan2(-rot[1], rot[0]) - rotation = Point3D(xAngle, yAngle, zAngle) + val matrix = VirtualMatrix(3, 3) { i, j -> rot[i * 3 + j] } + quaternion = Quaternion.fromRotationMatrix(matrix) } private fun Solid.translate(trans: DoubleArray) { val (x, y, z) = trans - position = Point3D(x, y, z) + position = Float32Vector3D(x, y, z) } private fun Solid.useMatrix(matrix: DGeoMatrix?) { @@ -72,7 +76,7 @@ private fun Solid.useMatrix(matrix: DGeoMatrix?) { val fScale by matrix.meta.doubleArray() translate(fTranslation) rotate(fRotationMatrix) - scale = Point3D(fScale[0], fScale[1], fScale[2]) + scale = Float32Vector3D(fScale[0], fScale[1], fScale[2]) } } } @@ -208,9 +212,9 @@ private fun SolidGroup.addShape( require(fNz > 1) { "The polyhedron geometry requires at least two planes" } val baseRadius = fRmax[0] shape { - (0..fNedges).forEach { - val phi = deltaphi * fNedges * it + startphi - (baseRadius * cos(phi) to baseRadius * sin(phi)) + (0.. @@ -223,7 +227,7 @@ private fun SolidGroup.addShape( "TGeoShapeAssembly" -> { val fVolume by shape.dObject(::DGeoVolume) fVolume?.let { volume -> - addRootVolume(volume, context, block = block) + addRootVolume(volume, context, name = volume.fName.ifEmpty { null }, block = block) } } @@ -248,14 +252,14 @@ private fun SolidGroup.addShape( val fDz by shape.meta.double(0.0) //TODO check proper node order - val node1 = Point3D(-fBl1, -fH1, -fDz) - val node2 = Point3D(fBl1, -fH1, -fDz) - val node3 = Point3D(fTl1, fH1, -fDz) - val node4 = Point3D(-fTl1, fH1, -fDz) - val node5 = Point3D(-fBl2, -fH2, fDz) - val node6 = Point3D(fBl2, -fH2, fDz) - val node7 = Point3D(fTl2, fH2, fDz) - val node8 = Point3D(-fTl2, fH2, fDz) + val node1 = Float32Vector3D(-fBl1, -fH1, -fDz) + val node2 = Float32Vector3D(fBl1, -fH1, -fDz) + val node3 = Float32Vector3D(fTl1, fH1, -fDz) + val node4 = Float32Vector3D(-fTl1, fH1, -fDz) + val node5 = Float32Vector3D(-fBl2, -fH2, fDz) + val node6 = Float32Vector3D(fBl2, -fH2, fDz) + val node7 = Float32Vector3D(fTl2, fH2, fDz) + val node8 = Float32Vector3D(-fTl2, fH2, fDz) hexagon(node1, node2, node3, node4, node5, node6, node7, node8, name) } @@ -264,7 +268,7 @@ private fun SolidGroup.addShape( val fScale by shape.dObject(::DGeoScale) fShape?.let { scaledShape -> solidGroup(name?.let { Name.parse(it) }) { - scale = Point3D(fScale?.x ?: 1.0, fScale?.y ?: 1.0, fScale?.z ?: 1.0) + scale = Float32Vector3D(fScale?.x ?: 1.0, fScale?.y ?: 1.0, fScale?.z ?: 1.0) addShape(scaledShape, context) apply(block) } @@ -328,7 +332,7 @@ private fun buildVolume(volume: DGeoVolume, context: RootToSolidContext): Solid? group }.apply { volume.fMedium?.let { medium -> - color.set(context.colorCache.getOrPut(medium.meta) { RootColors[11 + context.colorCache.size] }) + color(context.colorCache.getOrPut(medium.meta) { RootColors[11 + context.colorCache.size] }) } if (!context.ignoreRootColors) { @@ -348,29 +352,29 @@ private fun SolidGroup.addRootVolume( cache: Boolean = true, block: Solid.() -> Unit = {}, ) { - - val combinedName = if (volume.fName.isEmpty()) { - name - } else if (name == null) { - volume.fName - } else { - "${name}_${volume.fName}" + val combinedName = name?.parseAsName()?.let { + // this fix is required to work around malformed root files with duplicated node names + if (get(it) != null) { + it.withIndex(volume.hashCode().toString(16)) + } else { + it + } } if (!cache) { - val group = buildVolume(volume, context)?.apply(block) - setChild(combinedName?.let { Name.parse(it) }, group) + val group = buildVolume(volume, context)?.apply(block) ?: return + setChild(combinedName, group) } else { val templateName = volumesName + volume.name - val existing = getPrototype(templateName) + val existing = context.prototypeHolder.getPrototype(templateName) if (existing == null) { context.prototypeHolder.prototypes { - val group = buildVolume(volume, context) + val group = buildVolume(volume, context) ?: return@prototypes setChild(templateName, group) } } - ref(templateName, name).apply(block) + ref(templateName, combinedName).apply(block) } } diff --git a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/serialization/rootToSolid.kt b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/serialization/rootToSolid.kt index 50a4002a..eb39b8e7 100644 --- a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/serialization/rootToSolid.kt +++ b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/serialization/rootToSolid.kt @@ -25,12 +25,12 @@ private fun Solid.rotate(rot: DoubleArray) { val xAngle = atan2(-rot[5], rot[8]) val yAngle = atan2(rot[2], sqrt(1.0 - rot[2].pow(2))) val zAngle = atan2(-rot[1], rot[0]) - rotation = Point3D(xAngle, yAngle, zAngle) + rotation = Float32Vector3D(xAngle, yAngle, zAngle) } private fun Solid.translate(trans: DoubleArray) { val (x, y, z) = trans - position = Point3D(x, y, z) + position = Float32Vector3D(x, y, z) } private fun Solid.useMatrix(matrix: TGeoMatrix?) { @@ -52,7 +52,7 @@ private fun Solid.useMatrix(matrix: TGeoMatrix?) { translate(matrix.fTranslation) rotate(matrix.fRotationMatrix) val (xScale, yScale, zScale) = matrix.fScale - scale = Point3D(xScale, yScale, zScale) + scale = Float32Vector3D(xScale, yScale, zScale) } } } diff --git a/demo/gdml/src/jsMain/kotlin/space/kscience/visionforge/gdml/demo/GDMLAppComponent.kt b/demo/gdml/src/jsMain/kotlin/space/kscience/visionforge/gdml/demo/GDMLAppComponent.kt index 4301966a..37c178f4 100644 --- a/demo/gdml/src/jsMain/kotlin/space/kscience/visionforge/gdml/demo/GDMLAppComponent.kt +++ b/demo/gdml/src/jsMain/kotlin/space/kscience/visionforge/gdml/demo/GDMLAppComponent.kt @@ -23,7 +23,7 @@ import space.kscience.visionforge.setAsRoot import space.kscience.visionforge.solid.Solid import space.kscience.visionforge.solid.Solids import space.kscience.visionforge.solid.ambientLight -import space.kscience.visionforge.solid.set +import space.kscience.visionforge.solid.invoke import styled.css import styled.styledDiv @@ -53,7 +53,7 @@ val GDMLApp = fc("GDMLApp") { props -> console.info("Marking layers for file $name") markLayers() ambientLight { - color.set(Colors.white) + color(Colors.white) } } } diff --git a/demo/gdml/src/jsMain/kotlin/space/kscience/visionforge/gdml/demo/GdmlJsDemoApp.kt b/demo/gdml/src/jsMain/kotlin/space/kscience/visionforge/gdml/demo/GdmlJsDemoApp.kt index cdca1957..7c6514bf 100644 --- a/demo/gdml/src/jsMain/kotlin/space/kscience/visionforge/gdml/demo/GdmlJsDemoApp.kt +++ b/demo/gdml/src/jsMain/kotlin/space/kscience/visionforge/gdml/demo/GdmlJsDemoApp.kt @@ -12,7 +12,7 @@ import space.kscience.visionforge.react.createRoot import space.kscience.visionforge.react.render import space.kscience.visionforge.solid.Solids import space.kscience.visionforge.solid.ambientLight -import space.kscience.visionforge.solid.set +import space.kscience.visionforge.solid.invoke import space.kscience.visionforge.solid.three.ThreePlugin import space.kscience.visionforge.startApplication import styled.injectGlobal @@ -49,7 +49,7 @@ private class GDMLDemoApp : Application { child(GDMLApp) { val vision = GdmlShowCase.cubes().toVision().apply { ambientLight { - color.set(Colors.white) + color(Colors.white) } } //println(context.plugins.fetch(VisionManager).encodeToString(vision)) diff --git a/demo/js-playground/build.gradle.kts b/demo/js-playground/build.gradle.kts index 86935c51..94835588 100644 --- a/demo/js-playground/build.gradle.kts +++ b/demo/js-playground/build.gradle.kts @@ -7,7 +7,8 @@ kscience{ } kotlin{ - js(IR){ + explicitApi = null + js{ useCommonJs() browser { binaries.executable() diff --git a/demo/js-playground/src/main/kotlin/JsPlaygroundApp.kt b/demo/js-playground/src/main/kotlin/JsPlaygroundApp.kt index cb6eb3e6..d77d8f52 100644 --- a/demo/js-playground/src/main/kotlin/JsPlaygroundApp.kt +++ b/demo/js-playground/src/main/kotlin/JsPlaygroundApp.kt @@ -76,7 +76,7 @@ private class JsPlaygroundApp : Application { solids = playgroundContext.request(Solids) solid { ambientLight { - color.set(Colors.white) + color(Colors.white) } repeat(100) { sphere(5, name = "sphere[$it]") { @@ -84,7 +84,7 @@ private class JsPlaygroundApp : Application { y = random.nextDouble(-300.0, 300.0) z = random.nextDouble(-300.0, 300.0) material { - color.set(random.nextInt()) + color(random.nextInt()) } detail = 16 } diff --git a/demo/js-playground/src/main/kotlin/gravityDemo.kt b/demo/js-playground/src/main/kotlin/gravityDemo.kt index c04baf98..a4bc9057 100644 --- a/demo/js-playground/src/main/kotlin/gravityDemo.kt +++ b/demo/js-playground/src/main/kotlin/gravityDemo.kt @@ -42,13 +42,13 @@ val GravityDemo = fc { props -> solids = props.solids solid { pointLight(200, 200, 200, name = "light"){ - color.set(Colors.white) + color(Colors.white) } ambientLight() sphere(5.0, "ball") { detail = 16 - color.set("red") + color("red") val h = 100.0 y = h solids.context.launch { diff --git a/demo/muon-monitor/build.gradle.kts b/demo/muon-monitor/build.gradle.kts index 154d4b0a..c8f26b2c 100644 --- a/demo/muon-monitor/build.gradle.kts +++ b/demo/muon-monitor/build.gradle.kts @@ -40,6 +40,8 @@ kscience { } } +kotlin.explicitApi = null + application { mainClass.set("ru.mipt.npm.muon.monitor.server.MMServerKt") } diff --git a/demo/muon-monitor/src/commonMain/kotlin/ru/mipt/npm/muon/monitor/Event.kt b/demo/muon-monitor/src/commonMain/kotlin/ru/mipt/npm/muon/monitor/Event.kt index b47c2b66..22a9d2e1 100644 --- a/demo/muon-monitor/src/commonMain/kotlin/ru/mipt/npm/muon/monitor/Event.kt +++ b/demo/muon-monitor/src/commonMain/kotlin/ru/mipt/npm/muon/monitor/Event.kt @@ -1,9 +1,9 @@ package ru.mipt.npm.muon.monitor import kotlinx.serialization.Serializable -import space.kscience.visionforge.solid.Point3D +import space.kscience.visionforge.solid.Float32Vector3D -typealias Track = List +typealias Track = List /** * diff --git a/demo/muon-monitor/src/commonMain/kotlin/ru/mipt/npm/muon/monitor/Model.kt b/demo/muon-monitor/src/commonMain/kotlin/ru/mipt/npm/muon/monitor/Model.kt index 1b8eb566..f11c9e01 100644 --- a/demo/muon-monitor/src/commonMain/kotlin/ru/mipt/npm/muon/monitor/Model.kt +++ b/demo/muon-monitor/src/commonMain/kotlin/ru/mipt/npm/muon/monitor/Model.kt @@ -16,7 +16,7 @@ class Model(val manager: VisionManager) { private fun MutableVisionContainer.pixel(pixel: SC1) { val group = solidGroup(pixel.name) { - position = Point3D(pixel.center.x, pixel.center.y, pixel.center.z) + position = Float32Vector3D(pixel.center.x, pixel.center.y, pixel.center.z) box(pixel.xSize, pixel.ySize, pixel.zSize) label(pixel.name) { z = -Monitor.PIXEL_Z_SIZE / 2 - 5 @@ -39,7 +39,7 @@ class Model(val manager: VisionManager) { val root: SolidGroup = SolidGroup().apply { setAsRoot(this@Model.manager) material { - color.set("darkgreen") + color("darkgreen") } rotationX = PI / 2 solidGroup("bottom") { @@ -64,7 +64,7 @@ class Model(val manager: VisionManager) { private fun highlight(pixel: String) { println("highlight $pixel") - map[pixel]?.color.set("blue") + map[pixel]?.color("blue") } fun reset() { @@ -82,7 +82,7 @@ class Model(val manager: VisionManager) { } event.track?.let { tracks.polyline(*it.toTypedArray(), name = "track[${event.id}]") { - color.set("red") + color("red") } } } diff --git a/demo/muon-monitor/src/commonMain/kotlin/ru/mipt/npm/muon/monitor/Monitor.kt b/demo/muon-monitor/src/commonMain/kotlin/ru/mipt/npm/muon/monitor/Monitor.kt index 1a8c8aa9..48fe83d1 100644 --- a/demo/muon-monitor/src/commonMain/kotlin/ru/mipt/npm/muon/monitor/Monitor.kt +++ b/demo/muon-monitor/src/commonMain/kotlin/ru/mipt/npm/muon/monitor/Monitor.kt @@ -2,21 +2,21 @@ 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.Point3D -import space.kscience.visionforge.solid.plus +import space.kscience.visionforge.solid.Float32Euclidean3DSpace +import space.kscience.visionforge.solid.Float32Vector3D /** * A single pixel */ class SC1( val name: String, - val center: Point3D, + val center: Float32Vector3D, val xSize: Float = PIXEL_XY_SIZE, val ySize: Float = PIXEL_XY_SIZE, val zSize: Float = PIXEL_Z_SIZE, ) class SC16( val name: String, - val center: Point3D, + val center: Float32Vector3D, ) { /** @@ -109,9 +109,9 @@ class SC16( else -> throw Error() } - val offset = Point3D(-y, x, 0)//rotateDetector(Point3D(x, y, 0.0)); + val offset = Float32Vector3D(-y, x, 0)//rotateDetector(Point3D(x, y, 0.0)); val pixelName = "${name}_${index}" - SC1(pixelName, offset + center) + SC1(pixelName, with(Float32Euclidean3DSpace) { offset + center }) } } } @@ -154,7 +154,7 @@ object Monitor { val x = split[4].toDouble() - 500 val y = split[5].toDouble() - 500 val z = 180 - split[6].toDouble() - SC16(detectorName, Point3D(x, y, z)) + SC16(detectorName, Float32Vector3D(x, y, z)) } else { null } diff --git a/demo/muon-monitor/src/jsMain/kotlin/ru/mipt/npm/muon/monitor/MMAppComponent.kt b/demo/muon-monitor/src/jsMain/kotlin/ru/mipt/npm/muon/monitor/MMAppComponent.kt index 4c9649c6..07dc7c7c 100644 --- a/demo/muon-monitor/src/jsMain/kotlin/ru/mipt/npm/muon/monitor/MMAppComponent.kt +++ b/demo/muon-monitor/src/jsMain/kotlin/ru/mipt/npm/muon/monitor/MMAppComponent.kt @@ -26,7 +26,7 @@ import space.kscience.visionforge.ring.tab import space.kscience.visionforge.solid.Solids import space.kscience.visionforge.solid.ambientLight import space.kscience.visionforge.solid.edges -import space.kscience.visionforge.solid.set +import space.kscience.visionforge.solid.invoke import space.kscience.visionforge.solid.specifications.Canvas3DOptions import styled.css import styled.styledDiv @@ -58,7 +58,7 @@ val MMApp = fc("Muon monitor") { props -> props.model.root.apply { edges() ambientLight{ - color.set(Colors.white) + color(Colors.white) } } } diff --git a/demo/muon-monitor/src/jvmMain/kotlin/ru/mipt/npm/muon/monitor/MMServer.kt b/demo/muon-monitor/src/jvmMain/kotlin/ru/mipt/npm/muon/monitor/MMServer.kt index b9b8ce4c..d3d50c5b 100644 --- a/demo/muon-monitor/src/jvmMain/kotlin/ru/mipt/npm/muon/monitor/MMServer.kt +++ b/demo/muon-monitor/src/jvmMain/kotlin/ru/mipt/npm/muon/monitor/MMServer.kt @@ -10,8 +10,7 @@ import io.ktor.server.application.install import io.ktor.server.application.log import io.ktor.server.cio.CIO import io.ktor.server.engine.embeddedServer -import io.ktor.server.http.content.resources -import io.ktor.server.http.content.static +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 @@ -53,9 +52,7 @@ fun Application.module(context: Context = Global) { status = HttpStatusCode.OK ) } - static("/") { - resources() - } + staticResources("/", null) } try { Desktop.getDesktop().browse(URI("http://localhost:8080/index.html")) diff --git a/demo/muon-monitor/src/jvmMain/kotlin/ru/mipt/npm/muon/monitor/sim/line.kt b/demo/muon-monitor/src/jvmMain/kotlin/ru/mipt/npm/muon/monitor/sim/line.kt index c2578783..d9492a74 100644 --- a/demo/muon-monitor/src/jvmMain/kotlin/ru/mipt/npm/muon/monitor/sim/line.kt +++ b/demo/muon-monitor/src/jvmMain/kotlin/ru/mipt/npm/muon/monitor/sim/line.kt @@ -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.Point3D +import space.kscience.visionforge.solid.Float32Vector3D /** * Created by darksnake on 11-May-16. @@ -50,12 +50,12 @@ fun makeTrack(x: Double, y: Double, theta: Double, phi: Double): Line { ) } -fun Vector3D.toPoint() = Point3D(x, y, z) +fun Vector3D.toKMathVector() = Float32Vector3D(x, y, z) -fun Line.toPoints(): List { +fun Line.toKMathVectors(): List { val basePoint = basePlane.intersection(this) val bottom = basePoint.subtract(2000.0, direction) val top = basePoint.add(2000.0, direction) - return listOf(bottom.toPoint(), top.toPoint()) + return listOf(bottom.toKMathVector(), top.toKMathVector()) } diff --git a/demo/muon-monitor/src/jvmMain/kotlin/ru/mipt/npm/muon/monitor/sim/monitor.kt b/demo/muon-monitor/src/jvmMain/kotlin/ru/mipt/npm/muon/monitor/sim/monitor.kt index d2ec7235..e55b74db 100644 --- a/demo/muon-monitor/src/jvmMain/kotlin/ru/mipt/npm/muon/monitor/sim/monitor.kt +++ b/demo/muon-monitor/src/jvmMain/kotlin/ru/mipt/npm/muon/monitor/sim/monitor.kt @@ -43,7 +43,7 @@ fun readEffs(): Map { fun buildEventByTrack(index: Int, track: Line, hitResolver: (Line) -> Collection = defaultHitResolver): Event { - return Event(index, track.toPoints(), hitResolver(track).map { it.name }) + return Event(index, track.toKMathVectors(), hitResolver(track).map { it.name }) } val defaultHitResolver: (Line) -> Collection = { track: Line -> diff --git a/demo/playground/build.gradle.kts b/demo/playground/build.gradle.kts index ddc1d6a8..36bd6b7b 100644 --- a/demo/playground/build.gradle.kts +++ b/demo/playground/build.gradle.kts @@ -47,12 +47,11 @@ kotlin { val commonMain by getting { dependencies { implementation(projects.visionforgeSolid) - implementation(projects.visionforgeGdml) implementation(projects.visionforgePlotly) implementation(projects.visionforgeMarkdown) implementation(projects.visionforgeTables) implementation(projects.cernRootLoader) - implementation(projects.jupyter) + api(projects.visionforgeJupyter.visionforgeJupyterCommon) } } @@ -66,6 +65,8 @@ kotlin { val jvmMain by getting { dependencies { + implementation("io.ktor:ktor-server-cio:${spclibs.versions.ktor.get()}") + implementation(projects.visionforgeGdml) implementation(projects.visionforgeServer) implementation(spclibs.logback.classic) implementation("com.github.Ricky12Awesome:json-schema-serialization:0.6.6") diff --git a/demo/playground/notebooks/common-demo.ipynb b/demo/playground/notebooks/common-demo.ipynb new file mode 100644 index 00000000..78797545 --- /dev/null +++ b/demo/playground/notebooks/common-demo.ipynb @@ -0,0 +1,101 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "@file:Repository(\"*mavenLocal\")\n", + "@file:Repository(\"https://repo.kotlin.link\")\n", + "@file:Repository(\"https://maven.pkg.jetbrains.space/spc/p/sci/dev\")\n", + "@file:DependsOn(\"space.kscience:visionforge-jupyter-common-jvm:0.3.0-dev-12\")\n", + "//import space.kscience.visionforge.jupyter.JupyterCommonIntegration\n", + "//\n", + "//val integration = JupyterCommonIntegration()\n", + "//USE(integration.getDefinitions(notebook).first())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "vf.fragment {\n", + " h1 { +\"AAA\" }\n", + " vision {\n", + " solid {\n", + " ambientLight()\n", + " box(100, 100, 200)\n", + "\n", + " sphere(100) {\n", + " x = 300\n", + " }\n", + " }\n", + " }\n", + "\n", + " vision {\n", + " plotly {\n", + " scatter {\n", + " x(1, 2, 3, 1)\n", + " y(1, 2, 3, 4)\n", + " }\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "Plotly.plot { \n", + " scatter{\n", + " x(1,2,3)\n", + " y(1,2,3)\n", + " }\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Kotlin", + "language": "kotlin", + "name": "kotlin" + }, + "ktnbPluginMetadata": { + "isAddProjectLibrariesToClasspath": false + }, + "language_info": { + "codemirror_mode": "text/x-kotlin", + "file_extension": ".kt", + "mimetype": "text/x-kotlin", + "name": "kotlin", + "nbconvert_exporter": "", + "pygments_lexer": "kotlin", + "version": "1.8.20" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/demo/playground/notebooks/dynamic-demo.ipynb b/demo/playground/notebooks/dynamic-demo.ipynb index 41289185..ac70b4c2 100644 --- a/demo/playground/notebooks/dynamic-demo.ipynb +++ b/demo/playground/notebooks/dynamic-demo.ipynb @@ -2,15 +2,11 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": { "tags": [], "pycharm": { "is_executing": true - }, - "ExecuteTime": { - "end_time": "2023-05-29T15:22:37.933397300Z", - "start_time": "2023-05-29T15:22:37.913872100Z" } }, "outputs": [], @@ -18,57 +14,23 @@ }, { "cell_type": "code", - "execution_count": 3, - "metadata": { - "ExecuteTime": { - "end_time": "2023-05-29T15:22:50.486483300Z", - "start_time": "2023-05-29T15:22:50.457485500Z" - } - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Line_2.jupyter.kts (1:1 - 3) Unresolved reference: vf" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "vf.startServer()" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false - }, - "ExecuteTime": { - "end_time": "2023-05-29T15:22:51.410680600Z", - "start_time": "2023-05-29T15:22:51.250779400Z" } }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Line_3.jupyter.kts (1:16 - 26) Unresolved reference: coroutines\n", - "Line_3.jupyter.kts (4:1 - 7) Unresolved reference: Plotly\n", - "Line_3.jupyter.kts (5:5 - 12) Unresolved reference: scatter\n", - "Line_3.jupyter.kts (6:9 - 10) Unresolved reference: x\n", - "Line_3.jupyter.kts (7:9 - 10) Unresolved reference: y\n", - "Line_3.jupyter.kts (8:12 - 14) Unresolved reference: vf\n", - "Line_3.jupyter.kts (9:13 - 15) Unresolved reference: vf\n", - "Line_3.jupyter.kts (10:23 - 31) Unresolved reference: isActive\n", - "Line_3.jupyter.kts (11:21 - 26) Unresolved reference: delay\n", - "Line_3.jupyter.kts (12:21 - 22) Unresolved reference: y" - ] - } - ], + "outputs": [], "source": [ "import kotlinx.coroutines.*\n", "import kotlin.random.Random\n", diff --git a/demo/playground/src/jsMain/kotlin/playgroundMain.kt b/demo/playground/src/jsMain/kotlin/playgroundMain.kt index 8e259d48..feff4de1 100644 --- a/demo/playground/src/jsMain/kotlin/playgroundMain.kt +++ b/demo/playground/src/jsMain/kotlin/playgroundMain.kt @@ -1,5 +1,5 @@ import space.kscience.dataforge.misc.DFExperimental -import space.kscience.visionforge.jupyter.VFNotebookPlugin +import space.kscience.visionforge.jupyter.VFNotebookClient import space.kscience.visionforge.markup.MarkupPlugin import space.kscience.visionforge.plotly.PlotlyPlugin import space.kscience.visionforge.ring.ThreeWithControlsPlugin @@ -12,5 +12,5 @@ fun main() = runVisionClient { plugin(PlotlyPlugin) plugin(MarkupPlugin) plugin(TableVisionJsPlugin) - plugin(VFNotebookPlugin) + plugin(VFNotebookClient) } \ No newline at end of file diff --git a/demo/playground/src/jvmMain/kotlin/VisionForgePlayGroundForJupyter.kt b/demo/playground/src/jvmMain/kotlin/VisionForgePlayGroundForJupyter.kt deleted file mode 100644 index 0b87e06d..00000000 --- a/demo/playground/src/jvmMain/kotlin/VisionForgePlayGroundForJupyter.kt +++ /dev/null @@ -1,51 +0,0 @@ -package space.kscience.visionforge.examples - -import org.jetbrains.kotlinx.jupyter.api.libraries.resources -import space.kscience.dataforge.context.Context -import space.kscience.dataforge.misc.DFExperimental -import space.kscience.gdml.Gdml -import space.kscience.plotly.Plot -import space.kscience.visionforge.gdml.toVision -import space.kscience.visionforge.jupyter.VFIntegrationBase -import space.kscience.visionforge.plotly.PlotlyPlugin -import space.kscience.visionforge.plotly.asVision -import space.kscience.visionforge.solid.Solids -import space.kscience.visionforge.visionManager - -@DFExperimental -internal class VisionForgePlayGroundForJupyter : VFIntegrationBase( - Context("VisionForge") { - plugin(Solids) - plugin(PlotlyPlugin) - }.visionManager -) { - - override fun Builder.afterLoaded() { - resources { - js("VisionForge") { - classPath("js/visionforge-playground.js") - } - } - - import( - "space.kscience.gdml.*", - "space.kscience.plotly.*", - "space.kscience.plotly.models.*", - "space.kscience.visionforge.solid.*", - ) - - - render { gdmlModel -> - handler.produceHtml { - vision { gdmlModel.toVision() } - } - } - - render { plot -> - handler.produceHtml { - vision { plot.asVision() } - } - } - } - -} diff --git a/demo/playground/src/jvmMain/kotlin/antenna.kt b/demo/playground/src/jvmMain/kotlin/antenna.kt new file mode 100644 index 00000000..5ec997f7 --- /dev/null +++ b/demo/playground/src/jvmMain/kotlin/antenna.kt @@ -0,0 +1,102 @@ +package space.kscience.visionforge.examples + +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import space.kscience.dataforge.meta.configure +import space.kscience.kmath.complex.Quaternion +import space.kscience.kmath.complex.QuaternionField +import space.kscience.kmath.complex.conjugate +import space.kscience.kmath.geometry.* +import space.kscience.visionforge.solid.* +import kotlin.math.PI +import kotlin.math.cos +import kotlin.math.sin + +fun main() = serve { + +// val azimuth = 60.degrees +// val inclination = 15.degrees + +// val direction = with(QuaternionField) { +// Quaternion.fromRotation(-azimuth, Euclidean3DSpace.zAxis) * +// Quaternion.fromRotation(Angle.piDiv2 - inclination, Euclidean3DSpace.yAxis) +// } + + //val direction2 = Quaternion.fromEuler(Angle.zero, Angle.piDiv2 - inclination, -azimuth, RotationOrder.ZYX) + + val target = Quaternion.fromEuler((-45).degrees, 45.degrees, Angle.zero, RotationOrder.XYZ) + + + vision("canvas") { + requirePlugin(Solids) + + solid(options = { + configure { "controls.enabled" put false } + }) { + rotationX = -PI / 2 + rotationZ = PI + //axes(200) + ambientLight() + val platform = solidGroup("platform") { + cylinder(50, 5, name = "base") + solidGroup("frame") { + z = 60 + + solidGroup("antenna") { + axes(200) + tube(40, 10, 30) + sphereLayer(100, 95, theta = PI / 6) { + z = 100 + rotationX = -PI / 2 + } + cylinder(5, 30) { + z = 15 + } + + sphereLayer(101, 94, phi = PI / 32, theta = PI / 6) { + z = 100 + rotationX = -PI / 2 + color("red") + } + + quaternion = target + } + } + } + + val frame = platform["frame"] as SolidGroup + + val antenna = frame["antenna"] as SolidGroup + + val xPeriod = 5000 //ms + val yPeriod = 7000 //ms + + val incRot = Quaternion.fromRotation(30.degrees, Euclidean3DSpace.zAxis) + + + val rotationJob = context.launch { + var time: Long = 0L + while (isActive) { + with(QuaternionField) { + delay(200) + platform.quaternion = Quaternion.fromRotation( + 15.degrees * sin(time.toDouble() * 2 * PI / xPeriod), + Euclidean3DSpace.xAxis + ) * Quaternion.fromRotation( + 15.degrees * cos(time * 2 * PI / yPeriod), + Euclidean3DSpace.yAxis + ) + + val qi = platform.quaternion * incRot + + antenna.quaternion = qi.conjugate * incRot.conjugate * target + + time += 200 + //antenna.quaternion = Quaternion.fromRotation(5.degrees, Euclidean3DSpace.zAxis) * antenna.quaternion + } + } + } + } + } +} \ No newline at end of file diff --git a/demo/playground/src/jvmMain/kotlin/axes.kt b/demo/playground/src/jvmMain/kotlin/axes.kt new file mode 100644 index 00000000..b8178887 --- /dev/null +++ b/demo/playground/src/jvmMain/kotlin/axes.kt @@ -0,0 +1,22 @@ +package space.kscience.visionforge.examples + +import space.kscience.kmath.geometry.Euclidean3DSpace +import space.kscience.kmath.geometry.radians +import space.kscience.visionforge.html.ResourceLocation +import space.kscience.visionforge.solid.* +import kotlin.math.PI + +fun main() = makeVisionFile(resourceLocation = ResourceLocation.SYSTEM) { + vision("canvas") { + requirePlugin(Solids) + solid { + axes(100, "root-axes") + solidGroup("group") { + z = 100 + rotate((PI / 4).radians, Euclidean3DSpace.vector(1, 1, 1)) + axes(100, "local-axes") + box(50, 50, 50, "box") + } + } + } +} \ No newline at end of file diff --git a/demo/playground/src/jvmMain/kotlin/extruded.kt b/demo/playground/src/jvmMain/kotlin/extruded.kt new file mode 100644 index 00000000..8a25549f --- /dev/null +++ b/demo/playground/src/jvmMain/kotlin/extruded.kt @@ -0,0 +1,21 @@ +package space.kscience.visionforge.examples + +import space.kscience.visionforge.solid.ambientLight +import space.kscience.visionforge.solid.extruded +import space.kscience.visionforge.solid.polygon +import space.kscience.visionforge.solid.solid + +fun main() = makeVisionFile { + vision("canvas") { + solid { + ambientLight() + extruded("extruded") { + shape{ + polygon(8, 100) + layer(-30) + layer(30) + } + } + } + } +} \ No newline at end of file diff --git a/demo/playground/src/jvmMain/kotlin/gdmlCurve.kt b/demo/playground/src/jvmMain/kotlin/gdmlCurve.kt index c2af2a8f..4cac02b1 100644 --- a/demo/playground/src/jvmMain/kotlin/gdmlCurve.kt +++ b/demo/playground/src/jvmMain/kotlin/gdmlCurve.kt @@ -7,7 +7,7 @@ import space.kscience.visionforge.gdml.toVision import space.kscience.visionforge.html.ResourceLocation import space.kscience.visionforge.solid.Solids import space.kscience.visionforge.solid.color -import space.kscience.visionforge.solid.set +import space.kscience.visionforge.solid.invoke import space.kscience.visionforge.visible import java.nio.file.Path @@ -229,7 +229,7 @@ fun main() = makeVisionFile(Path.of("curves.html"), resourceLocation = ResourceL visible = false } if(solid.name.startsWith("gas")){ - color.set("green") + color("green") } else { //make all solids semi-transparent transparent() diff --git a/demo/playground/src/jvmMain/kotlin/randomSpheres.kt b/demo/playground/src/jvmMain/kotlin/randomSpheres.kt index 47201ff8..fd1b9865 100644 --- a/demo/playground/src/jvmMain/kotlin/randomSpheres.kt +++ b/demo/playground/src/jvmMain/kotlin/randomSpheres.kt @@ -19,7 +19,7 @@ fun main() = makeVisionFile( vision { solid { ambientLight { - color.set(Colors.white) + color(Colors.white) } repeat(100) { sphere(5, name = "sphere[$it]") { @@ -27,7 +27,7 @@ fun main() = makeVisionFile( y = random.nextDouble(-300.0, 300.0) z = random.nextDouble(-300.0, 300.0) material { - color.set(random.nextInt()) + color(random.nextInt()) } detail = 16 } diff --git a/demo/playground/src/jvmMain/kotlin/rootParser.kt b/demo/playground/src/jvmMain/kotlin/rootParser.kt index be70faf8..1faa8d0b 100644 --- a/demo/playground/src/jvmMain/kotlin/rootParser.kt +++ b/demo/playground/src/jvmMain/kotlin/rootParser.kt @@ -11,7 +11,7 @@ import space.kscience.visionforge.Colors import space.kscience.visionforge.html.ResourceLocation import space.kscience.visionforge.solid.Solids import space.kscience.visionforge.solid.ambientLight -import space.kscience.visionforge.solid.set +import space.kscience.visionforge.solid.invoke import space.kscience.visionforge.solid.solid import java.util.zip.ZipInputStream import kotlin.io.path.Path @@ -26,7 +26,7 @@ private fun Meta.countTypes(): Sequence = sequence { } fun main() { - val string = ZipInputStream(TGeoManager::class.java.getResourceAsStream("/root/BM@N_geometry.zip")!!).use { + val string = ZipInputStream(TGeoManager::class.java.getResourceAsStream("/root/geometry_run_7-2076.zip")!!).use { it.nextEntry it.readAllBytes().decodeToString() } @@ -44,9 +44,9 @@ fun main() { requirePlugin(Solids) solid { ambientLight { - color.set(Colors.white) + color(Colors.white) } - rootGeo(geo,"BM@N", maxLayer = 3, ignoreRootColors = true).also { + rootGeo(geo,"BM@N", ignoreRootColors = true).also { Path("data/BM@N.vf.json").writeText(Solids.encodeToString(it)) } } diff --git a/demo/playground/src/jvmMain/kotlin/serverExtensions.kt b/demo/playground/src/jvmMain/kotlin/serverExtensions.kt index 9eb87730..c20f27c7 100644 --- a/demo/playground/src/jvmMain/kotlin/serverExtensions.kt +++ b/demo/playground/src/jvmMain/kotlin/serverExtensions.kt @@ -1,11 +1,24 @@ package space.kscience.visionforge.examples +import io.ktor.server.cio.CIO +import io.ktor.server.engine.embeddedServer +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.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 +import space.kscience.visionforge.solid.Solids +import space.kscience.visionforge.tables.TableVisionPlugin import space.kscience.visionforge.visionManager import java.awt.Desktop import java.nio.file.Path + public fun makeVisionFile( path: Path? = null, title: String = "VisionForge page", @@ -26,6 +39,45 @@ public fun makeVisionFile( if (show) Desktop.getDesktop().browse(actualPath.toFile().toURI()) } +public fun serve( + title: String = "VisionForge page", + show: Boolean = true, + content: HtmlVisionFragment, +) { + val context = Context("playground") { + plugin(Solids) + plugin(PlotlyPlugin) + plugin(MarkupPlugin) + plugin(TableVisionPlugin) + } + + val server = embeddedServer(CIO, port = 7779) { + routing { + staticResources("", null, null) + } + + visionPage( + context.visionManager, + VisionPage.scriptHeader("js/visionforge-playground.js") { + defer = true + }, + VisionPage.title(title), + visionFragment = content + ) + }.start(false) + + if (show) { + server.openInBrowser() + } + + println("Enter 'exit' to close server") + while (readlnOrNull() != "exit") { + // + } + + server.close() +} + //@DFExperimental //public fun Context.makeVisionFile( // vision: Vision, diff --git a/demo/playground/src/jvmMain/kotlin/shapes.kt b/demo/playground/src/jvmMain/kotlin/shapes.kt index a338d123..55ce28d5 100644 --- a/demo/playground/src/jvmMain/kotlin/shapes.kt +++ b/demo/playground/src/jvmMain/kotlin/shapes.kt @@ -10,23 +10,23 @@ fun main() = makeVisionFile { ambientLight() box(100.0, 100.0, 100.0) { z = -110.0 - color.set("teal") + color("teal") } sphere(50.0) { x = 110 detail = 16 - color.set("red") + color("red") } tube(50, height = 10, innerRadius = 25, angle = PI) { y = 110 detail = 16 rotationX = PI / 4 - color.set("blue") + color("blue") } sphereLayer(50, 40, theta = PI / 2) { rotationX = -PI * 3 / 4 z = 110 - color.set(Colors.pink) + color(Colors.pink) } diff --git a/demo/playground/src/jvmMain/kotlin/simpleCube.kt b/demo/playground/src/jvmMain/kotlin/simpleCube.kt index 5dae515c..e1fc91eb 100644 --- a/demo/playground/src/jvmMain/kotlin/simpleCube.kt +++ b/demo/playground/src/jvmMain/kotlin/simpleCube.kt @@ -2,8 +2,8 @@ package space.kscience.visionforge.examples import space.kscience.visionforge.html.ResourceLocation import space.kscience.visionforge.solid.box +import space.kscience.visionforge.solid.invoke import space.kscience.visionforge.solid.material -import space.kscience.visionforge.solid.set import space.kscience.visionforge.solid.solid fun main() = makeVisionFile(resourceLocation = ResourceLocation.SYSTEM) { @@ -11,7 +11,7 @@ fun main() = makeVisionFile(resourceLocation = ResourceLocation.SYSTEM) { solid { box(100, 100, 100) material { - emissiveColor.set("red") + emissiveColor("red") } } } diff --git a/demo/playground/src/jvmMain/resources/root/geometry_run_7-2076.zip b/demo/playground/src/jvmMain/resources/root/geometry_run_7-2076.zip new file mode 100644 index 00000000..1680404c Binary files /dev/null and b/demo/playground/src/jvmMain/resources/root/geometry_run_7-2076.zip differ diff --git a/demo/sat-demo/build.gradle.kts b/demo/sat-demo/build.gradle.kts index 6afadf27..5e881b63 100644 --- a/demo/sat-demo/build.gradle.kts +++ b/demo/sat-demo/build.gradle.kts @@ -8,13 +8,15 @@ kscience { // useSerialization { // json() // } + useKtor() dependencies{ + implementation("io.ktor:ktor-server-cio") implementation(projects.visionforgeThreejs.visionforgeThreejsServer) - implementation("ch.qos.logback:logback-classic:1.4.5") + implementation(spclibs.logback.classic) } } -group = "ru.mipt.npm" +group = "center.sciprog" application { mainClass.set("ru.mipt.npm.sat.SatServerKt") diff --git a/demo/sat-demo/src/main/kotlin/ru/mipt/npm/sat/geometry.kt b/demo/sat-demo/src/main/kotlin/ru/mipt/npm/sat/geometry.kt index 052fb6e0..d2c30422 100644 --- a/demo/sat-demo/src/main/kotlin/ru/mipt/npm/sat/geometry.kt +++ b/demo/sat-demo/src/main/kotlin/ru/mipt/npm/sat/geometry.kt @@ -15,7 +15,7 @@ internal fun Solids.visionOfSatellite( ySegmentSize: Number = xSegmentSize, fiberDiameter: Number = 1.0, ): SolidGroup = solidGroup { - color.set("darkgreen") + color("darkgreen") val transparent by style { this[SolidMaterial.MATERIAL_OPACITY_KEY] = 0.3 } diff --git a/demo/sat-demo/src/main/kotlin/ru/mipt/npm/sat/satServer.kt b/demo/sat-demo/src/main/kotlin/ru/mipt/npm/sat/satServer.kt index 9e0c8282..9f2f7e59 100644 --- a/demo/sat-demo/src/main/kotlin/ru/mipt/npm/sat/satServer.kt +++ b/demo/sat-demo/src/main/kotlin/ru/mipt/npm/sat/satServer.kt @@ -33,7 +33,7 @@ fun main() { //Create a geometry val sat = solids.visionOfSatellite(ySegments = 3).apply { ambientLight { - color.set(Colors.white) + color(Colors.white) } } val server = embeddedServer(CIO, port = 7777) { @@ -63,7 +63,7 @@ fun main() { val randomJ = Random.nextInt(1, 4) val target = Name.parse("layer[$randomLayer].segment[$randomI,$randomJ]") val targetVision = sat[target] as Solid - targetVision.color.set("red") + targetVision.color("red") delay(1000) //use to ensure that color is cleared targetVision.color.value = Null diff --git a/demo/sat-demo/src/main/kotlin/ru/mipt/npm/sat/static.kt b/demo/sat-demo/src/main/kotlin/ru/mipt/npm/sat/static.kt index 2a1fd240..89dc3a09 100644 --- a/demo/sat-demo/src/main/kotlin/ru/mipt/npm/sat/static.kt +++ b/demo/sat-demo/src/main/kotlin/ru/mipt/npm/sat/static.kt @@ -3,8 +3,8 @@ package ru.mipt.npm.sat import space.kscience.dataforge.misc.DFExperimental import space.kscience.visionforge.html.ResourceLocation import space.kscience.visionforge.solid.box +import space.kscience.visionforge.solid.invoke import space.kscience.visionforge.solid.material -import space.kscience.visionforge.solid.set import space.kscience.visionforge.solid.solid import space.kscience.visionforge.three.makeThreeJsFile @@ -14,7 +14,7 @@ fun main() = makeThreeJsFile(resourceLocation = ResourceLocation.SYSTEM) { solid { box(100, 100, 100) material { - emissiveColor.set("red") + emissiveColor("red") } } } diff --git a/demo/solid-showcase/build.gradle.kts b/demo/solid-showcase/build.gradle.kts index 78e7d163..05a02260 100644 --- a/demo/solid-showcase/build.gradle.kts +++ b/demo/solid-showcase/build.gradle.kts @@ -1,14 +1,14 @@ plugins { id("space.kscience.gradle.mpp") - application +// application } kscience { useCoroutines() - jvm { - withJava() + jvm() + js{ + binaries.executable() } - js() dependencies { implementation(projects.visionforgeSolid) implementation(projects.visionforgeGdml) @@ -19,6 +19,8 @@ kscience { } } -application { - mainClass.set("space.kscience.visionforge.solid.demo.FXDemoAppKt") -} \ No newline at end of file +kotlin.explicitApi = null + +//application { +// mainClass.set("space.kscience.visionforge.solid.demo.FXDemoAppKt") +//} \ No newline at end of file diff --git a/demo/solid-showcase/src/commonMain/kotlin/space/kscience/visionforge/solid/demo/demo.kt b/demo/solid-showcase/src/commonMain/kotlin/space/kscience/visionforge/solid/demo/demo.kt index ef009b82..9e2ecedb 100644 --- a/demo/solid-showcase/src/commonMain/kotlin/space/kscience/visionforge/solid/demo/demo.kt +++ b/demo/solid-showcase/src/commonMain/kotlin/space/kscience/visionforge/solid/demo/demo.kt @@ -4,6 +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.radians import space.kscience.visionforge.Colors import space.kscience.visionforge.solid.* import space.kscience.visionforge.solid.specifications.Canvas3DOptions @@ -21,7 +23,7 @@ fun VisionLayout.demo(name: String, title: String = name, block: SolidGro val vision = solids.solidGroup { block() ambientLight { - color.set(Colors.white) + color(Colors.white) } } render(Name.parse(name), vision, meta) @@ -47,23 +49,23 @@ fun VisionLayout.showcase() { ambientLight() box(100.0, 100.0, 100.0) { z = -110.0 - color.set("teal") + color("teal") } sphere(50.0) { x = 110 detail = 16 - color.set("red") + color("red") } tube(50, height = 10, innerRadius = 25, angle = PI) { y = 110 detail = 16 rotationX = PI / 4 - color.set("blue") + color("blue") } sphereLayer(50, 40, theta = PI / 2) { rotationX = -PI * 3 / 4 z = 110 - color.set(Colors.pink) + color(Colors.pink) } } @@ -78,7 +80,7 @@ fun VisionLayout.showcase() { visible = false x = 110.0 //override color for this cube - color.set(1530) + color(1530) GlobalScope.launch(Dispatchers.Main) { while (isActive) { @@ -93,7 +95,7 @@ fun VisionLayout.showcase() { val random = Random(111) while (isActive) { delay(1000) - group.color.set(random.nextInt(0, Int.MAX_VALUE)) + group.color(random.nextInt(0, Int.MAX_VALUE)) } } } @@ -103,9 +105,16 @@ fun VisionLayout.showcase() { solidGroup { x = 200 rotationY = PI / 4 + axes(200) box(100, 100, 100) { - rotationZ = PI / 4 - color.set(Colors.red) + rotate((PI / 4).radians, Euclidean3DSpace.zAxis) + GlobalScope.launch(Dispatchers.Main) { + while (isActive) { + delay(100) + rotate((PI/20).radians,Euclidean3DSpace.yAxis) + } + } + color(Colors.red) } } } @@ -118,7 +127,7 @@ fun VisionLayout.showcase() { for (i in 0..100) { layer(i * 5, 20 * sin(2 * PI / 100 * i), 20 * cos(2 * PI / 100 * i)) } - color.set(Colors.teal) + color(Colors.teal) rotationX = -PI / 2 } } @@ -127,13 +136,16 @@ fun VisionLayout.showcase() { sphere(100) { detail = 32 opacity = 0.4 - color.set(Colors.blue) + color(Colors.blue) } repeat(20) { - polyline(Point3D(100, 100, 100), Point3D(-100, -100, -100)) { + polyline( + Float32Vector3D(100, 100, 100), + Float32Vector3D(-100, -100, -100) + ) { thickness = 3.0 rotationX = it * PI2 / 20 - color.set(Colors.green) + color(Colors.green) //rotationY = it * PI2 / 20 } } @@ -147,6 +159,10 @@ fun VisionLayout.showcase() { z = 26 } } + + demo("STL", "STL loaded from URL") { + stl("https://ozeki.hu/attachments/116/Menger_sponge_sample.stl") + } } fun VisionLayout.showcaseCSG() { @@ -160,7 +176,7 @@ fun VisionLayout.showcaseCSG() { detail = 32 } material { - color.set(Colors.pink) + color(Colors.pink) } } composite(CompositeType.UNION) { @@ -170,7 +186,7 @@ fun VisionLayout.showcaseCSG() { sphere(50) { detail = 32 } - color.set("lightgreen") + color("lightgreen") opacity = 0.7 } composite(CompositeType.SUBTRACT) { @@ -181,7 +197,7 @@ fun VisionLayout.showcaseCSG() { sphere(50) { detail = 32 } - color.set("teal") + color("teal") opacity = 0.7 } } @@ -192,7 +208,7 @@ fun VisionLayout.showcaseCSG() { detail = 32 } box(100, 100, 100) - color.set("red") + color("red") opacity = 0.5 } } diff --git a/demo/solid-showcase/src/jsMain/kotlin/space/kscience/visionforge/solid/demo/VariableBox.kt b/demo/solid-showcase/src/jsMain/kotlin/space/kscience/visionforge/solid/demo/VariableBox.kt index 15e53129..701df81b 100644 --- a/demo/solid-showcase/src/jsMain/kotlin/space/kscience/visionforge/solid/demo/VariableBox.kt +++ b/demo/solid-showcase/src/jsMain/kotlin/space/kscience/visionforge/solid/demo/VariableBox.kt @@ -25,7 +25,7 @@ internal fun SolidGroup.varBox( internal class VariableBox(val xSize: Number, val ySize: Number) : ThreeJsVision() { - override fun render(three: ThreePlugin): Object3D { + override suspend fun render(three: ThreePlugin): Object3D { val geometry = BoxGeometry(xSize, ySize, 1) val material = ThreeMaterials.DEFAULT.clone() diff --git a/jupyter/src/jvmMain/kotlin/VFForNotebook.kt b/jupyter/src/jvmMain/kotlin/VFForNotebook.kt deleted file mode 100644 index 50619e44..00000000 --- a/jupyter/src/jvmMain/kotlin/VFForNotebook.kt +++ /dev/null @@ -1,144 +0,0 @@ -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.util.url -import io.ktor.server.websocket.WebSockets -import kotlinx.coroutines.CoroutineScope -import kotlinx.html.* -import kotlinx.html.stream.createHTML -import org.jetbrains.kotlinx.jupyter.api.HTML -import org.jetbrains.kotlinx.jupyter.api.MimeTypedResult -import space.kscience.dataforge.context.Context -import space.kscience.dataforge.context.ContextAware -import space.kscience.dataforge.context.info -import space.kscience.dataforge.context.logger -import space.kscience.dataforge.meta.get -import space.kscience.dataforge.meta.int -import space.kscience.dataforge.meta.string -import space.kscience.dataforge.names.Name -import space.kscience.visionforge.Vision -import space.kscience.visionforge.VisionManager -import space.kscience.visionforge.html.HtmlVisionFragment -import space.kscience.visionforge.html.visionFragment -import space.kscience.visionforge.server.VisionRoute -import space.kscience.visionforge.server.serveVisionData -import space.kscience.visionforge.visionManager -import kotlin.coroutines.CoroutineContext -import kotlin.random.Random -import kotlin.random.nextUInt - -internal fun TagConsumer<*>.renderScriptForId(id: String) { - script { - type = "text/javascript" - unsafe { +"VisionForge.renderAllVisionsById(\"$id\");" } - } -} - -/** - * A handler class that includes a server and common utilities - */ -public class VFForNotebook(override val context: Context) : ContextAware, CoroutineScope { - - public val visionManager: VisionManager = context.visionManager - - private var counter = 0 - - private var engine: ApplicationEngine? = null - - public var isolateFragments: Boolean = false - - override val coroutineContext: CoroutineContext get() = context.coroutineContext - - public fun legacyMode() { - isolateFragments = true - } - - public fun isServerRunning(): Boolean = engine != null - - public fun html(block: TagConsumer<*>.() -> Unit): MimeTypedResult = HTML(createHTML().apply(block).finalize()) - - public fun startServer( - host: String = context.properties["visionforge.host"].string ?: "localhost", - port: Int = context.properties["visionforge.port"].int ?: VisionRoute.DEFAULT_PORT, - ): MimeTypedResult = html { - if (engine != null) { - p { - style = "color: red;" - +"Stopping current VisionForge server" - } - } - - //val connector: EngineConnectorConfig = EngineConnectorConfig(host, port) - - engine?.stop(1000, 2000) - engine = context.embeddedServer(CIO, port, host) { - install(WebSockets) - }.start(false) - - p { - style = "color: blue;" - +"Starting VisionForge server on http://$host:$port" - } - } - - public fun stopServer() { - engine?.apply { - logger.info { "Stopping VisionForge server" } - stop(1000, 2000) - engine = null - } - } - - private fun produceHtmlString( - fragment: HtmlVisionFragment, - ): String = createHTML().apply { - val id = "fragment[${fragment.hashCode()}/${Random.nextUInt()}]" - div { - this.id = id - val engine = engine - if (engine != null) { - //if server exist, serve dynamically - //server.serveVisionsFromFragment(consumer, "content-${counter++}", fragment) - val cellRoute = "content-${counter++}" - - val collector: MutableMap = mutableMapOf() - - val url = engine.environment.connectors.first().let { - url { - protocol = URLProtocol.WS - host = it.host - port = it.port - pathSegments = listOf(cellRoute, "ws") - } - } - - engine.application.serveVisionData(VisionRoute(cellRoute, visionManager), collector) - - visionFragment( - visionManager, - embedData = true, - updatesUrl = url, - onVisionRendered = { name, vision -> collector[name] = vision }, - fragment = fragment - ) - } else { - //if not, use static rendering - visionFragment(visionManager, fragment = fragment) - } - } - renderScriptForId(id) - }.finalize() - - public fun produceHtml(isolated: Boolean? = null, fragment: HtmlVisionFragment): MimeTypedResult = - HTML(produceHtmlString(fragment), isolated ?: isolateFragments) - - public fun fragment(body: HtmlVisionFragment): MimeTypedResult = produceHtml(fragment = body) - public fun page(body: HtmlVisionFragment): MimeTypedResult = produceHtml(true, body) - - public fun form(builder: FORM.() -> Unit): HtmlFormFragment = - HtmlFormFragment("form[${counter++}]", builder = builder) -} \ No newline at end of file diff --git a/jupyter/src/jvmMain/kotlin/VFIntegrationBase.kt b/jupyter/src/jvmMain/kotlin/VFIntegrationBase.kt deleted file mode 100644 index bf6322e9..00000000 --- a/jupyter/src/jvmMain/kotlin/VFIntegrationBase.kt +++ /dev/null @@ -1,96 +0,0 @@ -package space.kscience.visionforge.jupyter - -import kotlinx.html.* -import kotlinx.html.stream.createHTML -import org.jetbrains.kotlinx.jupyter.api.HTML -import org.jetbrains.kotlinx.jupyter.api.declare -import org.jetbrains.kotlinx.jupyter.api.libraries.JupyterIntegration -import space.kscience.dataforge.context.Context -import space.kscience.dataforge.context.ContextAware -import space.kscience.dataforge.misc.DFExperimental -import space.kscience.visionforge.Vision -import space.kscience.visionforge.VisionManager -import space.kscience.visionforge.html.* -import kotlin.random.Random -import kotlin.random.nextUInt - -/** - * A base class for different Jupyter VF integrations - */ -@DFExperimental -public abstract class VFIntegrationBase( - public val visionManager: VisionManager, -) : JupyterIntegration(), ContextAware { - - override val context: Context get() = visionManager.context - protected val handler: VFForNotebook = VFForNotebook(visionManager.context) - - protected abstract fun Builder.afterLoaded() - - final override fun Builder.onLoaded() { - - onLoaded { - declare("VisionForge" to handler, "vf" to handler) - } - - onShutdown { - handler.stopServer() - } - - import( - "kotlinx.html.*", - "space.kscience.visionforge.html.*" - ) - - render { fragment -> - handler.produceHtml(fragment = fragment) - } - - render { fragment -> - handler.produceHtml(fragment = fragment) - } - - render { vision -> - handler.produceHtml { - vision(vision) - } - - } - - render { page -> - HTML(createHTML().apply { - head { - meta { - charset = "utf-8" - } - page.pageHeaders.values.forEach { - fragment(it) - } - } - body { - val id = "fragment[${page.hashCode()}/${Random.nextUInt()}]" - div { - this.id = id - visionFragment(visionManager, fragment = page.content) - } - renderScriptForId(id) - } - }.finalize(), true) - } - - render { fragment -> - handler.produceHtml { - if (!handler.isServerRunning()) { - p { - style = "color: red;" - +"The server is not running. Forms are not interactive. Start server with `VisionForge.startServer()." - } - } - fragment(fragment.formBody) - vision(fragment.vision) - } - } - - afterLoaded() - } -} \ No newline at end of file diff --git a/jupyter/visionforge-jupyter-gdml/README.md b/jupyter/visionforge-jupyter-gdml/README.md deleted file mode 100644 index cae8af86..00000000 --- a/jupyter/visionforge-jupyter-gdml/README.md +++ /dev/null @@ -1,32 +0,0 @@ -# Module visionforge-jupyter-gdml - -Jupyter api artifact for GDML rendering - -## Usage - -## Artifact: - -The Maven coordinates of this project are `space.kscience:visionforge-jupyter-gdml:0.2.0`. - -**Gradle Groovy:** -```groovy -repositories { - maven { url 'https://repo.kotlin.link' } - mavenCentral() -} - -dependencies { - implementation 'space.kscience:visionforge-jupyter-gdml:0.2.0' -} -``` -**Gradle Kotlin DSL:** -```kotlin -repositories { - maven("https://repo.kotlin.link") - mavenCentral() -} - -dependencies { - implementation("space.kscience:visionforge-jupyter-gdml:0.2.0") -} -``` diff --git a/jupyter/visionforge-jupyter-gdml/build.gradle.kts b/jupyter/visionforge-jupyter-gdml/build.gradle.kts deleted file mode 100644 index 65df0c80..00000000 --- a/jupyter/visionforge-jupyter-gdml/build.gradle.kts +++ /dev/null @@ -1,38 +0,0 @@ -plugins { - id("space.kscience.gradle.mpp") -} - -description = "Jupyter api artifact for GDML rendering" - -kscience { - fullStack("js/gdml-jupyter.js", - jsConfig = { useCommonJs() } - ) { - commonWebpackConfig { - sourceMaps = false - cssSupport { - enabled.set(false) - } - } - } - - commonMain{ - implementation(projects.visionforgeSolid) - implementation(projects.jupyter) - } - - jvmMain{ - implementation(projects.visionforgeGdml) - } - - jsMain{ - implementation(projects.visionforgeThreejs) - implementation(projects.ui.ring) - } - - jupyterLibrary("space.kscience.visionforge.gdml.jupyter.GdmlForJupyter") -} - -readme { - maturity = space.kscience.gradle.Maturity.EXPERIMENTAL -} \ No newline at end of file diff --git a/jupyter/visionforge-jupyter-gdml/src/jsMain/kotlin/gdmlJupyter.kt b/jupyter/visionforge-jupyter-gdml/src/jsMain/kotlin/gdmlJupyter.kt deleted file mode 100644 index d4ee507e..00000000 --- a/jupyter/visionforge-jupyter-gdml/src/jsMain/kotlin/gdmlJupyter.kt +++ /dev/null @@ -1,12 +0,0 @@ -package space.kscience.visionforge.gdml.jupyter - -import space.kscience.dataforge.misc.DFExperimental -import space.kscience.visionforge.ring.ThreeWithControlsPlugin -import space.kscience.visionforge.runVisionClient - -@DFExperimental -@JsExport -public fun main(): Unit = runVisionClient { - plugin(ThreeWithControlsPlugin) -} - diff --git a/jupyter/visionforge-jupyter-gdml/src/jvmMain/kotlin/GdmlForJupyter.kt b/jupyter/visionforge-jupyter-gdml/src/jvmMain/kotlin/GdmlForJupyter.kt deleted file mode 100644 index 0d55be2e..00000000 --- a/jupyter/visionforge-jupyter-gdml/src/jvmMain/kotlin/GdmlForJupyter.kt +++ /dev/null @@ -1,38 +0,0 @@ -package space.kscience.visionforge.gdml.jupyter - -import org.jetbrains.kotlinx.jupyter.api.libraries.resources -import space.kscience.dataforge.context.Context -import space.kscience.dataforge.misc.DFExperimental -import space.kscience.gdml.Gdml -import space.kscience.visionforge.gdml.toVision -import space.kscience.visionforge.jupyter.VFIntegrationBase -import space.kscience.visionforge.solid.Solids -import space.kscience.visionforge.visionManager - -@DFExperimental -internal class GdmlForJupyter : VFIntegrationBase( - Context("GDML") { - plugin(Solids) - }.visionManager -) { - - override fun Builder.afterLoaded() { - - resources { - js("three") { - classPath("js/gdml-jupyter.js") - } - } - - import( - "space.kscience.gdml.*", - "space.kscience.visionforge.gdml.jupyter.*" - ) - - render { gdmlModel -> - handler.produceHtml { - vision { gdmlModel.toVision() } - } - } - } -} diff --git a/jupyter/visionforge-jupyter-gdml/webpack.config.d/01.ring.js b/jupyter/visionforge-jupyter-gdml/webpack.config.d/01.ring.js deleted file mode 100644 index 41da041c..00000000 --- a/jupyter/visionforge-jupyter-gdml/webpack.config.d/01.ring.js +++ /dev/null @@ -1,3 +0,0 @@ -const ringConfig = require('@jetbrains/ring-ui/webpack.config').config; - -config.module.rules.push(...ringConfig.module.rules) \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index e9e3deba..31119a06 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -64,6 +64,6 @@ include( ":demo:playground", // ":demo:plotly-fx", ":demo:js-playground", - ":jupyter", - ":jupyter:visionforge-jupyter-gdml" + ":visionforge-jupyter", + ":visionforge-jupyter:visionforge-jupyter-common" ) diff --git a/ui/react/src/jsMain/kotlin/space/kscience/visionforge/react/ThreeCanvasComponent.kt b/ui/react/src/jsMain/kotlin/space/kscience/visionforge/react/ThreeCanvasComponent.kt index 50c8a60a..8cfa515d 100644 --- a/ui/react/src/jsMain/kotlin/space/kscience/visionforge/react/ThreeCanvasComponent.kt +++ b/ui/react/src/jsMain/kotlin/space/kscience/visionforge/react/ThreeCanvasComponent.kt @@ -2,7 +2,6 @@ package space.kscience.visionforge.react import kotlinx.css.* import org.w3c.dom.Element -import org.w3c.dom.HTMLElement import react.* import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.request @@ -29,7 +28,7 @@ public val ThreeCanvasComponent: FC = fc("ThreeCanvasComponent useEffect(props.solid, props.options, elementRef) { if (canvas == null) { - val element = elementRef.current as? HTMLElement ?: error("Canvas element not found") + val element = elementRef.current ?: error("Canvas element not found") canvas = ThreeCanvas(three, element, props.options ?: Canvas3DOptions()) } } diff --git a/ui/ring/src/jsMain/kotlin/space.kscience.visionforge.ring/ThreeWithControlsPlugin.kt b/ui/ring/src/jsMain/kotlin/space.kscience.visionforge.ring/ThreeWithControlsPlugin.kt index dd7f8e0d..94259b2f 100644 --- a/ui/ring/src/jsMain/kotlin/space.kscience.visionforge.ring/ThreeWithControlsPlugin.kt +++ b/ui/ring/src/jsMain/kotlin/space.kscience.visionforge.ring/ThreeWithControlsPlugin.kt @@ -7,12 +7,15 @@ 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.meta.boolean +import space.kscience.dataforge.meta.get import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.asName import space.kscience.visionforge.ElementVisionRenderer import space.kscience.visionforge.Vision import space.kscience.visionforge.react.render import space.kscience.visionforge.solid.Solid +import space.kscience.visionforge.solid.specifications.Canvas3DOptions import space.kscience.visionforge.solid.three.ThreePlugin public class ThreeWithControlsPlugin : AbstractPlugin(), ElementVisionRenderer { @@ -24,11 +27,16 @@ public class ThreeWithControlsPlugin : AbstractPlugin(), ElementVisionRenderer { if (vision is Solid) ElementVisionRenderer.DEFAULT_RATING * 2 else ElementVisionRenderer.ZERO_RATING override fun render(element: Element, name: Name, vision: Vision, meta: Meta) { - space.kscience.visionforge.react.createRoot(element).render { - child(ThreeCanvasWithControls) { - attrs { - this.solids = three.solids - this.builderOfSolid = context.async { vision as Solid} + if(meta["controls.enabled"].boolean == false){ + three.render(element, name, vision, meta) + } else { + space.kscience.visionforge.react.createRoot(element).render { + child(ThreeCanvasWithControls) { + attrs { + this.solids = three.solids + this.options = Canvas3DOptions.read(meta) + this.builderOfSolid = context.async { vision as Solid } + } } } } diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/Vision.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/Vision.kt index aa698409..ce5f588f 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/Vision.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/Vision.kt @@ -67,7 +67,7 @@ public var Vision.visible: Boolean? */ public fun Vision.onPropertyChange( scope: CoroutineScope? = manager?.context, - callback: (Name) -> Unit + callback: suspend (Name) -> Unit ): Job = properties.changes.onEach { callback(it) }.launchIn(scope ?: error("Orphan Vision can't observe properties")) \ No newline at end of file diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionManager.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionManager.kt index 2be8a6b3..e4ca1cdb 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionManager.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionManager.kt @@ -35,7 +35,6 @@ public class VisionManager(meta: Meta) : AbstractPlugin(meta), MutableVisionCont public val jsonFormat: Json get() = Json(defaultJson) { - encodeDefaults = false serializersModule = this@VisionManager.serializersModule } @@ -85,7 +84,6 @@ public class VisionManager(meta: Meta) : AbstractPlugin(meta), MutableVisionCont serializersModule = defaultSerialModule prettyPrint = true useArrayPolymorphism = false - encodeDefaults = false ignoreUnknownKeys = true explicitNulls = false } diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/HtmlFragment.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/HtmlFragment.kt index b733e6a4..ec3f3605 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/HtmlFragment.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/HtmlFragment.kt @@ -4,19 +4,28 @@ import kotlinx.html.FlowContent import kotlinx.html.TagConsumer import kotlinx.html.stream.createHTML -public typealias HtmlFragment = TagConsumer<*>.() -> Unit - -public fun HtmlFragment.renderToString(): String = createHTML().apply(this).finalize() - -public fun TagConsumer<*>.fragment(fragment: HtmlFragment) { - fragment() +/** + * A standalone HTML fragment + */ +public fun interface HtmlFragment { + public fun TagConsumer<*>.append() } -public fun FlowContent.fragment(fragment: HtmlFragment) { - fragment(consumer) -} +/** + * Convenience method to append fragment to the given [consumer] + */ +public fun HtmlFragment.appendTo(consumer: TagConsumer<*>): Unit = consumer.append() -public operator fun HtmlFragment.plus(other: HtmlFragment): HtmlFragment = { - this@plus() - other() +/** + * Create a string from this [HtmlFragment] + */ +public fun HtmlFragment.renderToString(): String = createHTML().apply { append() }.finalize() + +public fun TagConsumer<*>.appendFragment(fragment: HtmlFragment): Unit = fragment.appendTo(this) + +public fun FlowContent.appendFragment(fragment: HtmlFragment): Unit = fragment.appendTo(consumer) + +public operator fun HtmlFragment.plus(other: HtmlFragment): HtmlFragment = HtmlFragment { + this@plus.appendTo(this) + other.appendTo(this) } \ No newline at end of file diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/HtmlVisionRenderer.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/HtmlVisionRenderer.kt index d3c2427f..c02b6e64 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/HtmlVisionRenderer.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/HtmlVisionRenderer.kt @@ -2,18 +2,17 @@ package space.kscience.visionforge.html import kotlinx.html.* import space.kscience.dataforge.meta.Meta -import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.NameToken import space.kscience.dataforge.names.asName import space.kscience.visionforge.Vision import space.kscience.visionforge.VisionManager -public typealias HtmlVisionFragment = VisionTagConsumer<*>.() -> Unit - -@DFExperimental -public fun HtmlVisionFragment(content: VisionTagConsumer<*>.() -> Unit): HtmlVisionFragment = content +public fun interface HtmlVisionFragment{ + public fun VisionTagConsumer<*>.append() +} +public fun HtmlVisionFragment.appendTo(consumer: VisionTagConsumer<*>): Unit = consumer.append() /** * Render a fragment in the given consumer and return a map of extracted visions @@ -84,7 +83,7 @@ public fun TagConsumer<*>.visionFragment( } } - fragment(consumer) + fragment.appendTo(consumer) } public fun FlowContent.visionFragment( diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/VisionPage.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/VisionPage.kt index de18ced5..1ea216c5 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/VisionPage.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/VisionPage.kt @@ -17,7 +17,7 @@ public data class VisionPage( /** * Use a script with given [src] as a global header for all pages. */ - public fun scriptHeader(src: String, block: SCRIPT.() -> Unit = {}): HtmlFragment = { + public fun scriptHeader(src: String, block: SCRIPT.() -> Unit = {}): HtmlFragment = HtmlFragment{ script { type = "text/javascript" this.src = src @@ -26,9 +26,9 @@ public data class VisionPage( } /** - * Use css with given stylesheet link as a global header for all pages. + * Use css with the given stylesheet link as a global header for all pages. */ - public fun styleSheetHeader(href: String, block: LINK.() -> Unit = {}): HtmlFragment = { + public fun styleSheetHeader(href: String, block: LINK.() -> Unit = {}): HtmlFragment = HtmlFragment{ link { rel = "stylesheet" this.href = href @@ -36,7 +36,7 @@ public data class VisionPage( } } - public fun title(title:String): HtmlFragment = { + public fun title(title:String): HtmlFragment = HtmlFragment{ title(title) } } diff --git a/visionforge-core/src/commonTest/kotlin/space/kscience/visionforge/html/HtmlTagTest.kt b/visionforge-core/src/commonTest/kotlin/space/kscience/visionforge/html/HtmlTagTest.kt index efc7cfd0..eaea0a4e 100644 --- a/visionforge-core/src/commonTest/kotlin/space/kscience/visionforge/html/HtmlTagTest.kt +++ b/visionforge-core/src/commonTest/kotlin/space/kscience/visionforge/html/HtmlTagTest.kt @@ -24,7 +24,7 @@ fun FlowContent.renderVisionFragment( renderer(name, vision, outputMeta) } } - fragment(consumer) + fragment.appendTo(consumer) return visionMap } @@ -35,7 +35,7 @@ private fun VisionOutput.base(block: VisionGroup.() -> Unit) = context.visionMan @DFExperimental class HtmlTagTest { - val fragment: HtmlVisionFragment = { + val fragment = HtmlVisionFragment{ div { h1 { +"Head" } vision("ddd") { diff --git a/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/VisionClient.kt b/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/VisionClient.kt index 7d61ce24..c9146efd 100644 --- a/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/VisionClient.kt +++ b/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/VisionClient.kt @@ -286,8 +286,8 @@ public fun VisionClient.renderAllVisionsIn(element: Element) { /** * Render all visions in an element with a given [id] */ -public fun VisionClient.renderAllVisionsById(id: String): Unit = whenDocumentLoaded { - val element = getElementById(id) +public fun VisionClient.renderAllVisionsById(document: Document, id: String): Unit { + val element = document.getElementById(id) if (element != null) { renderAllVisionsIn(element) } else { diff --git a/visionforge-core/src/jvmMain/kotlin/space/kscience/visionforge/html/HtmlVisionContext.kt b/visionforge-core/src/jvmMain/kotlin/space/kscience/visionforge/html/HtmlVisionContext.kt index 7d82da49..3796d436 100644 --- a/visionforge-core/src/jvmMain/kotlin/space/kscience/visionforge/html/HtmlVisionContext.kt +++ b/visionforge-core/src/jvmMain/kotlin/space/kscience/visionforge/html/HtmlVisionContext.kt @@ -34,10 +34,10 @@ public interface HtmlVisionContext : ContextAware { public typealias HtmlVisionContextFragment = context(HtmlVisionContext) TagConsumer<*>.() -> Unit -context(HtmlVisionContext) -public fun HtmlVisionFragment( - content: TagConsumer<*>.() -> Unit, -): HtmlVisionFragment = content +//context(HtmlVisionContext) +//public fun HtmlVisionFragment( +// content: TagConsumer<*>.() -> Unit, +//): HtmlVisionFragment = HtmlVisionFragment { } context(HtmlVisionContext) private fun TagConsumer.vision( diff --git a/visionforge-core/src/jvmMain/kotlin/space/kscience/visionforge/html/headers.kt b/visionforge-core/src/jvmMain/kotlin/space/kscience/visionforge/html/headers.kt index 4c4ab90a..bf880e84 100644 --- a/visionforge-core/src/jvmMain/kotlin/space/kscience/visionforge/html/headers.kt +++ b/visionforge-core/src/jvmMain/kotlin/space/kscience/visionforge/html/headers.kt @@ -91,14 +91,14 @@ internal fun checkOrStoreFile(htmlPath: Path, filePath: Path, resource: String, */ internal fun fileScriptHeader( path: Path, -): HtmlFragment = { +): HtmlFragment = HtmlFragment{ script { type = "text/javascript" src = path.toString() } } -internal fun embedScriptHeader(resource: String, classLoader: ClassLoader): HtmlFragment = { +internal fun embedScriptHeader(resource: String, classLoader: ClassLoader): HtmlFragment = HtmlFragment{ script { type = "text/javascript" unsafe { @@ -113,7 +113,7 @@ internal fun fileCssHeader( cssPath: Path, resource: String, classLoader: ClassLoader, -): HtmlFragment = { +): HtmlFragment = HtmlFragment{ val relativePath = checkOrStoreFile(basePath, cssPath, resource, classLoader) link { rel = "stylesheet" diff --git a/visionforge-core/src/jvmMain/kotlin/space/kscience/visionforge/html/htmlExport.kt b/visionforge-core/src/jvmMain/kotlin/space/kscience/visionforge/html/htmlExport.kt index ec31b7b4..e45dd9b5 100644 --- a/visionforge-core/src/jvmMain/kotlin/space/kscience/visionforge/html/htmlExport.kt +++ b/visionforge-core/src/jvmMain/kotlin/space/kscience/visionforge/html/htmlExport.kt @@ -78,7 +78,7 @@ public fun VisionPage.makeFile( charset = "utf-8" } actualHeaders.values.forEach { - fragment(it) + appendFragment(it) } } body { diff --git a/visionforge-gdml/src/commonMain/kotlin/space/kscience/visionforge/gdml/GdmlLoaderOptions.kt b/visionforge-gdml/src/commonMain/kotlin/space/kscience/visionforge/gdml/GdmlLoaderOptions.kt index 56158c55..2d3c18c9 100644 --- a/visionforge-gdml/src/commonMain/kotlin/space/kscience/visionforge/gdml/GdmlLoaderOptions.kt +++ b/visionforge-gdml/src/commonMain/kotlin/space/kscience/visionforge/gdml/GdmlLoaderOptions.kt @@ -42,7 +42,7 @@ public class GdmlLoaderOptions { * Configure paint for given solid with given [GdmlMaterial] */ public var configurePaint: SolidMaterial.(material: GdmlMaterial, solid: GdmlSolid) -> Unit = - { material, _ -> color.set(randomColor(material)) } + { material, _ -> color(randomColor(material)) } private set public fun paint(block: SolidMaterial.(material: GdmlMaterial, solid: GdmlSolid) -> Unit) { diff --git a/visionforge-gdml/src/commonMain/kotlin/space/kscience/visionforge/gdml/gdmlLoader.kt b/visionforge-gdml/src/commonMain/kotlin/space/kscience/visionforge/gdml/gdmlLoader.kt index 58213437..0c81d258 100644 --- a/visionforge-gdml/src/commonMain/kotlin/space/kscience/visionforge/gdml/gdmlLoader.kt +++ b/visionforge-gdml/src/commonMain/kotlin/space/kscience/visionforge/gdml/gdmlLoader.kt @@ -6,6 +6,7 @@ import space.kscience.dataforge.names.asName import space.kscience.dataforge.names.plus import space.kscience.gdml.* +import space.kscience.kmath.geometry.RotationOrder import space.kscience.visionforge.* import space.kscience.visionforge.html.VisionOutput import space.kscience.visionforge.solid.* @@ -206,9 +207,9 @@ private class GdmlLoader(val settings: GdmlLoaderOptions) { require(solid.planes.size > 1) { "The polyhedron geometry requires at least two planes" } val baseRadius = solid.planes.first().rmax * lScale shape { - (0..solid.numsides).forEach { + (0.. @@ -248,14 +249,14 @@ private class GdmlLoader(val settings: GdmlLoaderOptions) { val dyBottom = solid.y1.toDouble() / 2 val dyTop = solid.y2.toDouble() / 2 val dz = solid.z.toDouble() / 2 - val node1 = Point3D(-dxBottom, -dyBottom, -dz) - val node2 = Point3D(dxBottom, -dyBottom, -dz) - val node3 = Point3D(dxBottom, dyBottom, -dz) - val node4 = Point3D(-dxBottom, dyBottom, -dz) - val node5 = Point3D(-dxTop, -dyTop, dz) - val node6 = Point3D(dxTop, -dyTop, dz) - val node7 = Point3D(dxTop, dyTop, dz) - val node8 = Point3D(-dxTop, dyTop, dz) + val node1 = Float32Vector3D(-dxBottom, -dyBottom, -dz) + val node2 = Float32Vector3D(dxBottom, -dyBottom, -dz) + val node3 = Float32Vector3D(dxBottom, dyBottom, -dz) + val node4 = Float32Vector3D(-dxBottom, dyBottom, -dz) + val node5 = Float32Vector3D(-dxTop, -dyTop, dz) + val node6 = Float32Vector3D(dxTop, -dyTop, dz) + val node7 = Float32Vector3D(dxTop, dyTop, dz) + val node8 = Float32Vector3D(-dxTop, dyTop, dz) hexagon(node1, node2, node3, node4, node5, node6, node7, node8, name) } diff --git a/jupyter/README.md b/visionforge-jupyter/README.md similarity index 100% rename from jupyter/README.md rename to visionforge-jupyter/README.md diff --git a/jupyter/build.gradle.kts b/visionforge-jupyter/build.gradle.kts similarity index 64% rename from jupyter/build.gradle.kts rename to visionforge-jupyter/build.gradle.kts index 1c32732b..49943631 100644 --- a/jupyter/build.gradle.kts +++ b/visionforge-jupyter/build.gradle.kts @@ -5,13 +5,17 @@ plugins { description = "Common visionforge jupyter module" kscience { + useKtor() jvm() js() jupyterLibrary() dependencies { api(projects.visionforgeCore) } - jvmMain { + dependencies(jvmMain){ + api("io.ktor:ktor-server-cio-jvm") + api("io.ktor:ktor-server-websockets-jvm") + api("io.ktor:ktor-server-cors-jvm") api(projects.visionforgeServer) } } diff --git a/jupyter/src/jsMain/kotlin/VFNotebookPlugin.kt b/visionforge-jupyter/src/jsMain/kotlin/VFNotebookClient.kt similarity index 72% rename from jupyter/src/jsMain/kotlin/VFNotebookPlugin.kt rename to visionforge-jupyter/src/jsMain/kotlin/VFNotebookClient.kt index f6a37f23..7f06c6ac 100644 --- a/jupyter/src/jsMain/kotlin/VFNotebookPlugin.kt +++ b/visionforge-jupyter/src/jsMain/kotlin/VFNotebookClient.kt @@ -1,6 +1,7 @@ package space.kscience.visionforge.jupyter import kotlinx.browser.window +import org.w3c.dom.Document import org.w3c.dom.Element import space.kscience.dataforge.context.AbstractPlugin import space.kscience.dataforge.context.Context @@ -13,15 +14,15 @@ import space.kscience.visionforge.renderAllVisionsById import space.kscience.visionforge.renderAllVisionsIn @JsExport -public class VFNotebookPlugin : AbstractPlugin() { +public class VFNotebookClient : AbstractPlugin() { private val client by require(VisionClient) public fun renderAllVisionsIn(element: Element) { client.renderAllVisionsIn(element) } - public fun renderAllVisionsById(id: String) { - client.renderAllVisionsById(id) + public fun renderAllVisionsById(document: Document, id: String) { + client.renderAllVisionsById(document, id) } public fun renderAllVisions() { @@ -30,17 +31,18 @@ public class VFNotebookPlugin : AbstractPlugin() { init { + console.info("Loading VisionForge global hooks") //register VisionForge in the browser window - window.asDynamic().vf = this - window.asDynamic().VisionForge = this + window.parent.asDynamic().vf = this + window.parent.asDynamic().VisionForge = this } @Suppress("NON_EXPORTABLE_TYPE") override val tag: PluginTag get() = Companion.tag @Suppress("NON_EXPORTABLE_TYPE") - public companion object : PluginFactory { - override fun build(context: Context, meta: Meta): VFNotebookPlugin = VFNotebookPlugin() + public companion object : PluginFactory { + override fun build(context: Context, meta: Meta): VFNotebookClient = VFNotebookClient() override val tag: PluginTag = PluginTag(name = "vision.notebook", group = PluginTag.DATAFORGE_GROUP) } diff --git a/visionforge-jupyter/src/jvmMain/kotlin/VisionForge.kt b/visionforge-jupyter/src/jvmMain/kotlin/VisionForge.kt new file mode 100644 index 00000000..49d5fe23 --- /dev/null +++ b/visionforge-jupyter/src/jvmMain/kotlin/VisionForge.kt @@ -0,0 +1,177 @@ +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.util.url +import io.ktor.server.websocket.WebSockets +import kotlinx.coroutines.CoroutineScope +import kotlinx.html.* +import kotlinx.html.stream.createHTML +import org.jetbrains.kotlinx.jupyter.api.* +import space.kscience.dataforge.context.Context +import space.kscience.dataforge.context.ContextAware +import space.kscience.dataforge.context.info +import space.kscience.dataforge.context.logger +import space.kscience.dataforge.meta.* +import space.kscience.dataforge.names.Name +import space.kscience.visionforge.Vision +import space.kscience.visionforge.VisionManager +import space.kscience.visionforge.html.HtmlVisionFragment +import space.kscience.visionforge.html.visionFragment +import space.kscience.visionforge.server.VisionRoute +import space.kscience.visionforge.server.serveVisionData +import kotlin.coroutines.CoroutineContext +import kotlin.random.Random +import kotlin.random.nextUInt + + +@Suppress("FunctionName") +internal inline fun HTML(isolated: Boolean = false, block: TagConsumer<*>.() -> Unit): MimeTypedResult = + HTML(createHTML().apply(block).finalize(), isolated) + +internal fun KotlinKernelHost.displayHtml(block: TagConsumer<*>.() -> Unit) { + display(HTML(false, block), null) +} + +public enum class VisionForgeCompatibility { + JUPYTER, + JUPYTER_LAB, + DATALORE, + IDEA +} + +/** + * A handler class that includes a server and common utilities + */ +@Suppress("ExtractKtorModule") +public class VisionForge( + public val visionManager: VisionManager, + public val notebook: Notebook, + meta: Meta = Meta.EMPTY, +) : ContextAware, CoroutineScope { + + override val context: Context get() = visionManager.context + + public val configuration: ObservableMutableMeta = meta.toMutableMeta() + + private var counter = 0 + + private var engine: ApplicationEngine? = null + + override val coroutineContext: CoroutineContext get() = context.coroutineContext + + + public fun isServerRunning(): Boolean = engine != null + + public fun getProperty(name: String): TypedMeta<*>? = configuration[name] ?: context.properties[name] + + internal fun startServer( + kernel: KotlinKernelHost, + host: String = getProperty("visionforge.host").string ?: "localhost", + port: Int = getProperty("visionforge.port").int ?: VisionRoute.DEFAULT_PORT, + ) { + engine?.let { + kernel.displayHtml { + p { + style = "color: red;" + +"Stopping current VisionForge server" + } + } + it.stop(1000, 2000) + } + + //val connector: EngineConnectorConfig = EngineConnectorConfig(host, port) + + + engine = context.embeddedServer(CIO, port, host) { + install(WebSockets) + }.start(false) + + kernel.displayHtml { + p { + style = "color: blue;" + +"Starting VisionForge server on port $port" + } + } + } + + internal fun stopServer(kernel: KotlinKernelHost) { + engine?.apply { + logger.info { "Stopping VisionForge server" } + stop(1000, 2000) + engine = null + } + + kernel.displayHtml { + p { + style = "color: red;" + +"VisionForge server stopped" + } + } + } + + internal fun TagConsumer<*>.renderScriptForId(id: String) { + script { + type = "text/javascript" + //language=JavaScript + unsafe { +"parent.VisionForge.renderAllVisionsById(document, \"$id\");" } + } + } + + + public fun produceHtml( + isolated: Boolean? = null, + fragment: HtmlVisionFragment, + ): MimeTypedResult { + val iframeIsolation = isolated ?: when (notebook.jupyterClientType) { + JupyterClientType.DATALORE, JupyterClientType.JUPYTER_NOTEBOOK -> true + else -> false + } + return HTML( + iframeIsolation + ) { + val id = "fragment[${fragment.hashCode()}/${Random.nextUInt()}]" + div { + this.id = id + val engine = engine + if (engine != null) { + //if server exist, serve dynamically + //server.serveVisionsFromFragment(consumer, "content-${counter++}", fragment) + val cellRoute = "content-${counter++}" + + val collector: MutableMap = mutableMapOf() + + val url = engine.environment.connectors.first().let { + url { + protocol = URLProtocol.WS + host = it.host + port = it.port + pathSegments = listOf(cellRoute, "ws") + } + } + + engine.application.serveVisionData(VisionRoute(cellRoute, visionManager), collector) + + visionFragment( + visionManager, + embedData = true, + updatesUrl = url, + onVisionRendered = { name, vision -> collector[name] = vision }, + fragment = fragment + ) + } else { + //if not, use static rendering + visionFragment(visionManager, fragment = fragment) + } + } + renderScriptForId(id) + } + } + + public fun form(builder: FORM.() -> Unit): HtmlFormFragment = + HtmlFormFragment("form[${counter++}]", builder = builder) +} + diff --git a/visionforge-jupyter/src/jvmMain/kotlin/VisionForgeIntegration.kt b/visionforge-jupyter/src/jvmMain/kotlin/VisionForgeIntegration.kt new file mode 100644 index 00000000..afc2ecc2 --- /dev/null +++ b/visionforge-jupyter/src/jvmMain/kotlin/VisionForgeIntegration.kt @@ -0,0 +1,133 @@ +package space.kscience.visionforge.jupyter + +import kotlinx.html.* +import org.jetbrains.kotlinx.jupyter.api.KotlinKernelHost +import org.jetbrains.kotlinx.jupyter.api.MimeTypedResult +import org.jetbrains.kotlinx.jupyter.api.declare +import org.jetbrains.kotlinx.jupyter.api.libraries.JupyterIntegration +import space.kscience.dataforge.context.Context +import space.kscience.dataforge.context.ContextAware +import space.kscience.dataforge.misc.DFExperimental +import space.kscience.visionforge.Vision +import space.kscience.visionforge.VisionManager +import space.kscience.visionforge.html.* +import kotlin.random.Random +import kotlin.random.nextUInt + +/** + * A base class for different Jupyter VF integrations + */ +@DFExperimental +public abstract class VisionForgeIntegration( + public val visionManager: VisionManager, +) : JupyterIntegration(), ContextAware { + + override val context: Context get() = visionManager.context + + protected abstract fun Builder.afterLoaded(vf: VisionForge) + + final override fun Builder.onLoaded() { + + val vf: VisionForge = VisionForge(visionManager, notebook) + + onLoaded { + val kernel: KotlinKernelHost = this + declare("VisionForge" to vf, "vf" to vf) + vf.startServer(kernel) + vf.configuration.onChange(this) { name -> + if (name.toString() == "visionforge.port") { + kernel.displayHtml { + p { +"Property 'visionforge.port' changed. Restarting server" } + } + vf.startServer(kernel) + } + } + } + + + onShutdown { + vf.stopServer(this) + } + + import( + "kotlinx.html.*", + "space.kscience.visionforge.html.*", + "space.kscience.visionforge.jupyter.*" + ) +// +// render { fragment -> +// HTML(fragment.renderToString()) +// } +// +// render { fragment -> +// handler.produceHtml(fragment = fragment) +// } + + render { vision -> + vf.produceHtml { + vision(vision) + } + } + + render { page -> + HTML(true) { + head { + meta { + charset = "utf-8" + } + page.pageHeaders.values.forEach { + appendFragment(it) + } + } + body { + val id = "fragment[${page.hashCode()}/${Random.nextUInt()}]" + div { + this.id = id + visionFragment(visionManager, fragment = page.content) + } + with(vf) { + renderScriptForId(id) + } + } + } + } + + render { fragment -> + vf.produceHtml { + if (!vf.isServerRunning()) { + p { + style = "color: red;" + +"The server is not running. Forms are not interactive. Start server with `VisionForge.startServer()." + } + } + appendFragment(fragment.formBody) + vision(fragment.vision) + } + } + + afterLoaded(vf) + } +} + + +/** + * Create a fragment without a head to be embedded in the page + */ +@Suppress("UnusedReceiverParameter") +public fun VisionForge.html(body: TagConsumer<*>.() -> Unit): MimeTypedResult = HTML(false, body) + + +/** + * Create a fragment without a head to be embedded in the page + */ +public fun VisionForge.fragment(body: VisionTagConsumer<*>.() -> Unit): MimeTypedResult = produceHtml(false, body) + + +/** + * Create a standalone page in the notebook + */ +public fun VisionForge.page( + pageHeaders: Map = emptyMap(), + body: VisionTagConsumer<*>.() -> Unit, +): VisionPage = VisionPage(visionManager, pageHeaders, body) + diff --git a/jupyter/src/jvmMain/kotlin/forms.kt b/visionforge-jupyter/src/jvmMain/kotlin/forms.kt similarity index 82% rename from jupyter/src/jvmMain/kotlin/forms.kt rename to visionforge-jupyter/src/jvmMain/kotlin/forms.kt index bccce2e7..fe072887 100644 --- a/jupyter/src/jvmMain/kotlin/forms.kt +++ b/visionforge-jupyter/src/jvmMain/kotlin/forms.kt @@ -11,11 +11,14 @@ import space.kscience.visionforge.html.VisionOfHtmlForm public class HtmlFormFragment internal constructor( public val vision: VisionOfHtmlForm, public val formBody: HtmlFragment, -){ +) { public val values: Meta? get() = vision.values public operator fun get(valueName: String): Meta? = values?.get(valueName) } +/** + * Top level function to create a form + */ public fun HtmlFormFragment(id: String? = null, builder: FORM.() -> Unit): HtmlFormFragment { val realId = id ?: "form[${builder.hashCode().toUInt()}]" return HtmlFormFragment(VisionOfHtmlForm(realId)) { @@ -24,4 +27,8 @@ public fun HtmlFormFragment(id: String? = null, builder: FORM.() -> Unit): HtmlF builder() } } -} \ No newline at end of file +} + + +public fun VisionForge.form(id: String? = null, builder: FORM.() -> Unit): HtmlFormFragment = + HtmlFormFragment(id, builder) \ No newline at end of file diff --git a/visionforge-jupyter/visionforge-jupyter-common/build.gradle.kts b/visionforge-jupyter/visionforge-jupyter-common/build.gradle.kts new file mode 100644 index 00000000..e6fd162b --- /dev/null +++ b/visionforge-jupyter/visionforge-jupyter-common/build.gradle.kts @@ -0,0 +1,42 @@ +plugins { + id("space.kscience.gradle.mpp") +} + +description = "Jupyter api artifact including all common modules" + +kscience { + fullStack( + "js/visionforge-jupyter-common.js", + jsConfig = { useCommonJs() } + ) { + commonWebpackConfig { + sourceMaps = false + cssSupport { + enabled.set(false) + } + } + } + + dependencies { + api(projects.visionforgeSolid) + api(projects.visionforgePlotly) + api(projects.visionforgeTables) + api(projects.visionforgeMarkdown) + api(projects.visionforgeJupyter) + } + + jvmMain { + api(projects.visionforgeGdml) + } + + jsMain { + implementation(projects.ui.ring) + implementation(projects.visionforgeThreejs) + } + + jupyterLibrary("space.kscience.visionforge.jupyter.JupyterCommonIntegration") +} + +readme { + maturity = space.kscience.gradle.Maturity.EXPERIMENTAL +} \ No newline at end of file diff --git a/visionforge-jupyter/visionforge-jupyter-common/src/jsMain/kotlin/commonJupyter.kt b/visionforge-jupyter/visionforge-jupyter-common/src/jsMain/kotlin/commonJupyter.kt new file mode 100644 index 00000000..e5fb4edd --- /dev/null +++ b/visionforge-jupyter/visionforge-jupyter-common/src/jsMain/kotlin/commonJupyter.kt @@ -0,0 +1,17 @@ +package space.kscience.visionforge.gdml.jupyter + +import space.kscience.visionforge.jupyter.VFNotebookClient +import space.kscience.visionforge.markup.MarkupPlugin +import space.kscience.visionforge.plotly.PlotlyPlugin +import space.kscience.visionforge.ring.ThreeWithControlsPlugin +import space.kscience.visionforge.runVisionClient +import space.kscience.visionforge.tables.TableVisionJsPlugin + +public fun main(): Unit = runVisionClient { + plugin(ThreeWithControlsPlugin) + plugin(PlotlyPlugin) + plugin(MarkupPlugin) + plugin(TableVisionJsPlugin) + plugin(VFNotebookClient) +} + diff --git a/visionforge-jupyter/visionforge-jupyter-common/src/jvmMain/kotlin/JupyterCommonIntegration.kt b/visionforge-jupyter/visionforge-jupyter-common/src/jvmMain/kotlin/JupyterCommonIntegration.kt new file mode 100644 index 00000000..6200bd5d --- /dev/null +++ b/visionforge-jupyter/visionforge-jupyter-common/src/jvmMain/kotlin/JupyterCommonIntegration.kt @@ -0,0 +1,90 @@ +package space.kscience.visionforge.jupyter + +import kotlinx.html.* +import org.jetbrains.kotlinx.jupyter.api.libraries.resources +import space.kscience.dataforge.context.Context +import space.kscience.dataforge.misc.DFExperimental +import space.kscience.gdml.Gdml +import space.kscience.plotly.Plot +import space.kscience.plotly.PlotlyPage +import space.kscience.plotly.StaticPlotlyRenderer +import space.kscience.tables.* +import space.kscience.visionforge.gdml.toVision +import space.kscience.visionforge.html.HtmlFragment +import space.kscience.visionforge.html.VisionPage +import space.kscience.visionforge.markup.MarkupPlugin +import space.kscience.visionforge.plotly.PlotlyPlugin +import space.kscience.visionforge.plotly.asVision +import space.kscience.visionforge.solid.Solids +import space.kscience.visionforge.tables.TableVisionPlugin +import space.kscience.visionforge.tables.toVision +import space.kscience.visionforge.visionManager + + +@DFExperimental +public class JupyterCommonIntegration : VisionForgeIntegration(CONTEXT.visionManager) { + + override fun Builder.afterLoaded(vf: VisionForge) { + + resources { + js("visionforge-common") { + classPath("js/visionforge-jupyter-common.js") + } + } + + import( + "space.kscience.gdml.*", + "space.kscience.visionforge.solid.*", + "space.kscience.tables.*", + "space.kscience.dataforge.meta.*", + "space.kscience.plotly.*", + "space.kscience.plotly.models.*", + "space.kscience.visionforge.plotly.plotly" + ) + + render { gdmlModel -> + vf.produceHtml { + vision { gdmlModel.toVision() } + } + } + + render> { table -> + vf.produceHtml { + vision { table.toVision() } + } + } + + render { plot -> + vf.produceHtml { + vision { plot.asVision() } + } + } + + + render { plotlyPage -> + val headers = plotlyPage.headers.associate { plotlyFragment -> + plotlyFragment.hashCode().toString(16) to HtmlFragment { + plotlyFragment.visit(this) + } + + } + VisionPage(visionManager, headers) { + div{ + p { +"Plotly page renderer is not recommended in VisionForge, use `vf.page{}`" } + } + div { + plotlyPage.fragment.render.invoke(this, StaticPlotlyRenderer) + } + } + } + } + + public companion object { + private val CONTEXT: Context = Context("Jupyter-common") { + plugin(Solids) + plugin(PlotlyPlugin) + plugin(TableVisionPlugin) + plugin(MarkupPlugin) + } + } +} diff --git a/visionforge-jupyter/visionforge-jupyter-common/webpack.config.d/01.ring.js b/visionforge-jupyter/visionforge-jupyter-common/webpack.config.d/01.ring.js new file mode 100644 index 00000000..cfb15cb8 --- /dev/null +++ b/visionforge-jupyter/visionforge-jupyter-common/webpack.config.d/01.ring.js @@ -0,0 +1,24 @@ +const ringConfig = require('@jetbrains/ring-ui/webpack.config').config; + +const path = require('path'); + +config.module.rules.push(...ringConfig.module.rules) + +config.module.rules.push( + { + test: /\.css$/, + exclude: [ + path.resolve(__dirname, "../../node_modules/@jetbrains/ring-ui") + ], + use: [ + { + loader: 'style-loader', + options: {} + }, + { + loader: 'css-loader', + options: {} + } + ] + } +) \ No newline at end of file diff --git a/visionforge-server/build.gradle.kts b/visionforge-server/build.gradle.kts index a4a7d848..59034dd1 100644 --- a/visionforge-server/build.gradle.kts +++ b/visionforge-server/build.gradle.kts @@ -6,10 +6,10 @@ kscience{ useKtor() dependencies { api(projects.visionforgeCore) - api("io.ktor:ktor-server-cio") + api("io.ktor:ktor-server-host-common") api("io.ktor:ktor-server-html-builder") api("io.ktor:ktor-server-websockets") - implementation("io.ktor:ktor-server-cors") + api("io.ktor:ktor-server-cors") } } diff --git a/visionforge-server/src/main/kotlin/space/kscience/visionforge/server/VisionServer.kt b/visionforge-server/src/main/kotlin/space/kscience/visionforge/server/VisionServer.kt index 84a87a65..61952497 100644 --- a/visionforge-server/src/main/kotlin/space/kscience/visionforge/server/VisionServer.kt +++ b/visionforge-server/src/main/kotlin/space/kscience/visionforge/server/VisionServer.kt @@ -2,7 +2,6 @@ package space.kscience.visionforge.server import io.ktor.http.* import io.ktor.server.application.* -import io.ktor.server.cio.* import io.ktor.server.engine.* import io.ktor.server.html.* import io.ktor.server.http.content.* @@ -58,7 +57,7 @@ public class VisionRoute( override val context: Context get() = visionManager.context /** - * Update minimal interval between updates in milliseconds (if there are no updates, push will not happen + * Update the minimal interval between updates in milliseconds (if there are no updates, push will not happen */ public var updateInterval: Long by meta.long(300, key = UPDATE_INTERVAL_KEY) @@ -171,8 +170,8 @@ public fun Application.visionPage( meta { charset = "utf-8" } - headers.forEach { header -> - consumer.header() + headers.forEach { headerContent -> + headerContent.appendTo(consumer) } } body { diff --git a/visionforge-solid/build.gradle.kts b/visionforge-solid/build.gradle.kts index a1d4fbc4..f87cef77 100644 --- a/visionforge-solid/build.gradle.kts +++ b/visionforge-solid/build.gradle.kts @@ -2,6 +2,8 @@ plugins { id("space.kscience.gradle.mpp") } +val kmathVersion = "0.3.1" + kscience { jvm() js() @@ -11,6 +13,7 @@ kscience { } useCoroutines() dependencies { + api("space.kscience:kmath-geometry:0.3.1") api(projects.visionforgeCore) } dependencies(jvmTest) { diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/ColorAccessor.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/ColorAccessor.kt index 01abf310..60789027 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/ColorAccessor.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/ColorAccessor.kt @@ -28,7 +28,7 @@ public class ColorAccessor( } } -public fun Vision.color( +public fun Vision.colorProperty( propertyName: Name? = null, ): ReadOnlyProperty = ReadOnlyProperty { _, property -> ColorAccessor(properties.root(true), propertyName ?: property.name.asName()) @@ -43,21 +43,21 @@ public var ColorAccessor?.string: String? /** * Set [webcolor](https://en.wikipedia.org/wiki/Web_colors) as string */ -public fun ColorAccessor?.set(webColor: String) { +public operator fun ColorAccessor?.invoke(webColor: String) { this?.value = webColor.asValue() } /** * Set color as RGB integer */ -public fun ColorAccessor?.set(rgb: Int) { +public operator fun ColorAccessor?.invoke(rgb: Int) { this?.value = Colors.rgbToString(rgb).asValue() } /** * Set color as RGB */ -public fun ColorAccessor?.set(r: UByte, g: UByte, b: UByte) { +public operator fun ColorAccessor?.invoke(r: UByte, g: UByte, b: UByte) { this?.value = Colors.rgbToString(r, g, b).asValue() } diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/ConeSegment.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/ConeSegment.kt index 94ea5eaf..7b2679cd 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/ConeSegment.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/ConeSegment.kt @@ -36,8 +36,8 @@ public class ConeSegment( require(segments >= 4) { "The number of segments in cone is too small" } val angleStep = phi / (segments - 1) - fun shape(r: Float, z: Float): List = (0 until segments).map { i -> - Point3D(r * cos(phiStart + angleStep * i), r * sin(phiStart + angleStep * i), z) + fun shape(r: Float, z: Float): List = (0 until segments).map { i -> + Float32Vector3D(r * cos(phiStart + angleStep * i), r * sin(phiStart + angleStep * i), z) } geometryBuilder.apply { @@ -53,8 +53,8 @@ public class ConeSegment( if (phi == PI2) { face4(bottomPoints.last(), bottomPoints[0], topPoints[0], topPoints.last()) } - val zeroBottom = Point3D(0f, 0f, -height / 2) - val zeroTop = Point3D(0f, 0f, +height / 2) + val zeroBottom = Float32Vector3D(0f, 0f, -height / 2) + val zeroTop = Float32Vector3D(0f, 0f, +height / 2) for (it in 1 until segments) { face(bottomPoints[it - 1], zeroBottom, bottomPoints[it]) face(topPoints[it - 1], topPoints[it], zeroTop) diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/ConeSurface.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/ConeSurface.kt index 75ff2161..8ce9ba8c 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/ConeSurface.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/ConeSurface.kt @@ -38,8 +38,8 @@ public class ConeSurface( require(segments >= 4) { "The number of segments in tube is too small" } val angleStep = phi / (segments - 1) - fun shape(r: Float, z: Float): List = (0 until segments).map { i -> - Point3D(r * cos(phiStart + angleStep * i), r * sin(phiStart + angleStep * i), z) + fun shape(r: Float, z: Float): List = (0 until segments).map { i -> + Float32Vector3D(r * cos(phiStart + angleStep * i), r * sin(phiStart + angleStep * i), z) } geometryBuilder.apply { @@ -56,8 +56,8 @@ public class ConeSurface( face4(bottomOuterPoints.last(), bottomOuterPoints[0], topOuterPoints[0], topOuterPoints.last()) } if (bottomInnerRadius == 0f) { - val zeroBottom = Point3D(0f, 0f, -height / 2) - val zeroTop = Point3D(0f, 0f, height / 2) + val zeroBottom = Float32Vector3D(0f, 0f, -height / 2) + val zeroTop = Float32Vector3D(0f, 0f, height / 2) (1 until segments).forEach { face(bottomOuterPoints[it - 1], zeroBottom, bottomOuterPoints[it]) face(topOuterPoints[it - 1], topOuterPoints[it], zeroTop) diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Convex.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Convex.kt index 4d50c2c4..4fa174ac 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Convex.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Convex.kt @@ -7,7 +7,7 @@ import space.kscience.visionforge.setChild @Serializable @SerialName("solid.convex") -public class Convex(public val points: List) : SolidBase() +public class Convex(public val points: List) : SolidBase() public inline fun MutableVisionContainer.convex( name: String? = null, @@ -15,10 +15,10 @@ public inline fun MutableVisionContainer.convex( ): Convex = ConvexBuilder().apply(action).build().also { setChild(name, it) } public class ConvexBuilder { - private val points = ArrayList() + private val points = ArrayList() public fun point(x: Number, y: Number, z: Number) { - points.add(Point3D(x, y, z)) + points.add(Float32Vector3D(x, y, z)) } public fun build(): Convex { diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Extruded.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Extruded.kt index faec109b..014db562 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Extruded.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Extruded.kt @@ -4,23 +4,25 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import space.kscience.dataforge.meta.MutableMeta import space.kscience.dataforge.names.Name -import space.kscience.visionforge.* +import space.kscience.kmath.geometry.component1 +import space.kscience.kmath.geometry.component2 +import space.kscience.visionforge.MutableVisionContainer +import space.kscience.visionforge.VisionBuilder +import space.kscience.visionforge.setChild import kotlin.math.PI import kotlin.math.cos import kotlin.math.sin -public typealias Shape2D = List +public typealias Shape2D = List @Serializable -public class Shape2DBuilder(private val points: ArrayList = ArrayList()) { +public class Shape2DBuilder(private val points: ArrayList = ArrayList()) { public fun point(x: Number, y: Number) { - points.add(Point2D(x, y)) + points.add(Float32Vector2D(x, y)) } - public infix fun Number.to(y: Number): Unit = point(this, y) - public fun build(): Shape2D = points } @@ -38,7 +40,7 @@ public data class Layer(var x: Float, var y: Float, var z: Float, var scale: Flo @Serializable @SerialName("solid.extrude") public class Extruded( - public val shape: List, + public val shape: List, public val layers: List, ) : SolidBase(), GeometrySolid { @@ -50,18 +52,18 @@ public class Extruded( /** * Expand the shape for specific layers */ - val layers: List> = layers.map { layer -> + val layers: List> = layers.map { layer -> shape.map { (x, y) -> val newX = layer.x + x * layer.scale val newY = layer.y + y * layer.scale - Point3D(newX, newY, layer.z) + Float32Vector3D(newX, newY, layer.z) } } if (layers.size < 2) error("Extruded shape requires more than one layer") var lowerLayer = layers.first() - var upperLayer: List + var upperLayer: List for (i in (1 until layers.size)) { upperLayer = layers[i] @@ -94,7 +96,7 @@ public class Extruded( } public class ExtrudeBuilder( - public var shape: List = emptyList(), + public var shape: List = emptyList(), public var layers: MutableList = ArrayList(), public val properties: MutableMeta = MutableMeta(), ) { diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Float32Euclidean2DSpace.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Float32Euclidean2DSpace.kt new file mode 100644 index 00000000..b9a2fdba --- /dev/null +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Float32Euclidean2DSpace.kt @@ -0,0 +1,71 @@ +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 + + +public object Float32Euclidean2DSpace : + GeometrySpace, + ScaleOperations { + + @Serializable + @SerialName("Float32Vector2D") + private data class Vector2DImpl( + override val x: Float, + override val y: Float, + ) : Float32Vector2D + + public object VectorSerializer : KSerializer { + 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) diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Float32Euclidean3DSpace.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Float32Euclidean3DSpace.kt new file mode 100644 index 00000000..04b3df35 --- /dev/null +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Float32Euclidean3DSpace.kt @@ -0,0 +1,113 @@ +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 + + +public object Float32Euclidean3DSpace : + GeometrySpace, + ScaleOperations{ + + @Serializable + @SerialName("Float32Vector3D") + private data class Vector3DImpl( + override val x: Float, + override val y: Float, + override val z: Float, + ) : Float32Vector3D + + public object VectorSerializer : KSerializer { + 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) diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/GeometryBuilder.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/GeometryBuilder.kt index 3cfc5da9..ab24f8ce 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/GeometryBuilder.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/GeometryBuilder.kt @@ -13,17 +13,17 @@ public interface GeometryBuilder { * @param normal optional external normal to the face * @param meta optional additional platform-specific parameters like color or texture index */ - public fun face(vertex1: Point3D, vertex2: Point3D, vertex3: Point3D, normal: Point3D? = null, meta: Meta = Meta.EMPTY) + public fun face(vertex1: Float32Vector3D, vertex2: Float32Vector3D, vertex3: Float32Vector3D, normal: Float32Vector3D? = null, meta: Meta = Meta.EMPTY) public fun build(): T } public fun GeometryBuilder<*>.face4( - vertex1: Point3D, - vertex2: Point3D, - vertex3: Point3D, - vertex4: Point3D, - normal: Point3D? = null, + vertex1: Float32Vector3D, + vertex2: Float32Vector3D, + vertex3: Float32Vector3D, + vertex4: Float32Vector3D, + normal: Float32Vector3D? = null, meta: Meta = Meta.EMPTY ) { face(vertex1, vertex2, vertex3, normal, meta) @@ -37,9 +37,9 @@ public interface GeometrySolid : Solid { public fun toGeometry(geometryBuilder: GeometryBuilder) } -public fun GeometryBuilder.cap(shape: List, normal: Point3D? = null) { +public fun GeometryBuilder.cap(shape: List, normal: Float32Vector3D? = null) { //FIXME won't work for non-convex shapes - val center = Point3D( + val center = Float32Vector3D( shape.map { it.x }.average(), shape.map { it.y }.average(), shape.map { it.z }.average() diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Hexagon.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Hexagon.kt index 92f11e1b..12e661eb 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Hexagon.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Hexagon.kt @@ -7,14 +7,14 @@ import space.kscience.visionforge.VisionBuilder import space.kscience.visionforge.setChild public interface Hexagon : GeometrySolid { - public val node1: Point3D - public val node2: Point3D - public val node3: Point3D - public val node4: Point3D - public val node5: Point3D - public val node6: Point3D - public val node7: Point3D - public val node8: Point3D + public val node1: Float32Vector3D + public val node2: Float32Vector3D + public val node3: Float32Vector3D + public val node4: Float32Vector3D + public val node5: Float32Vector3D + public val node6: Float32Vector3D + public val node7: Float32Vector3D + public val node8: Float32Vector3D override fun toGeometry(geometryBuilder: GeometryBuilder) { geometryBuilder.face4(node1, node4, node3, node2) @@ -41,14 +41,14 @@ public class Box( private inline val dy get() = ySize / 2 private inline val dz get() = zSize / 2 - override val node1: Point3D get() = Point3D(-dx, -dy, -dz) - override val node2: Point3D get() = Point3D(dx, -dy, -dz) - override val node3: Point3D get() = Point3D(dx, dy, -dz) - override val node4: Point3D get() = Point3D(-dx, dy, -dz) - override val node5: Point3D get() = Point3D(-dx, -dy, dz) - override val node6: Point3D get() = Point3D(dx, -dy, dz) - override val node7: Point3D get() = Point3D(dx, dy, dz) - override val node8: Point3D get() = Point3D(-dx, dy, dz) + override val node1: Float32Vector3D get() = Float32Vector3D(-dx, -dy, -dz) + override val node2: Float32Vector3D get() = Float32Vector3D(dx, -dy, -dz) + override val node3: Float32Vector3D get() = Float32Vector3D(dx, dy, -dz) + override val node4: Float32Vector3D get() = Float32Vector3D(-dx, dy, -dz) + override val node5: Float32Vector3D get() = Float32Vector3D(-dx, -dy, dz) + override val node6: Float32Vector3D get() = Float32Vector3D(dx, -dy, dz) + override val node7: Float32Vector3D get() = Float32Vector3D(dx, dy, dz) + override val node8: Float32Vector3D get() = Float32Vector3D(-dx, dy, dz) } @VisionBuilder @@ -63,26 +63,26 @@ public inline fun MutableVisionContainer.box( @Serializable @SerialName("solid.hexagon") public class GenericHexagon( - override val node1: Point3D, - override val node2: Point3D, - override val node3: Point3D, - override val node4: Point3D, - override val node5: Point3D, - override val node6: Point3D, - override val node7: Point3D, - override val node8: Point3D, + override val node1: Float32Vector3D, + override val node2: Float32Vector3D, + override val node3: Float32Vector3D, + override val node4: Float32Vector3D, + override val node5: Float32Vector3D, + override val node6: Float32Vector3D, + override val node7: Float32Vector3D, + override val node8: Float32Vector3D, ) : SolidBase(), Hexagon @VisionBuilder public inline fun MutableVisionContainer.hexagon( - node1: Point3D, - node2: Point3D, - node3: Point3D, - node4: Point3D, - node5: Point3D, - node6: Point3D, - node7: Point3D, - node8: Point3D, + node1: Float32Vector3D, + node2: Float32Vector3D, + node3: Float32Vector3D, + node4: Float32Vector3D, + node5: Float32Vector3D, + node6: Float32Vector3D, + node7: Float32Vector3D, + node8: Float32Vector3D, name: String? = null, action: Hexagon.() -> Unit = {}, ): Hexagon = GenericHexagon(node1, node2, node3, node4, node5, node6, node7, node8).apply(action).also { setChild(name, it) } \ No newline at end of file diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/LightSource.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/LightSource.kt index 95aca618..6064e6ed 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/LightSource.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/LightSource.kt @@ -15,7 +15,7 @@ import space.kscience.visionforge.* public abstract class LightSource : SolidBase() { override val descriptor: MetaDescriptor get() = LightSource.descriptor - public val color: ColorAccessor by color(SolidMaterial.COLOR_KEY) + public val color: ColorAccessor by colorProperty(SolidMaterial.COLOR_KEY) public var intensity: Number by properties.root(includeStyles = false).number(INTENSITY_KEY) { 1.0 } public companion object { @@ -70,6 +70,6 @@ public fun MutableVisionContainer.pointLight( name: String? = null, block: PointLightSource.() -> Unit = {}, ): PointLightSource = PointLightSource().apply(block).also { - it.position = Point3D(x, y, z) + it.position = Float32Vector3D(x, y, z) setChild(name, it) } \ No newline at end of file diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/MiscSolid.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/MiscSolid.kt new file mode 100644 index 00000000..f83f6cb9 --- /dev/null +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/MiscSolid.kt @@ -0,0 +1,26 @@ +package space.kscience.visionforge.solid + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import space.kscience.visionforge.MutableVisionContainer +import space.kscience.visionforge.VisionBuilder +import space.kscience.visionforge.setChild + +public abstract class MiscSolid: SolidBase() + +@Serializable +@SerialName("solid.axes") +public class AxesSolid(public val size: Double): MiscSolid(){ + public companion object{ + public const val AXES_NAME: String = "@xes" + } +} + +@VisionBuilder +public fun MutableVisionContainer.axes( + size: Number, + name: String = "@axes", + block: AxesSolid.() -> Unit = {}, +): AxesSolid = AxesSolid(size.toDouble()).apply(block).also { + setChild(name, it) +} \ No newline at end of file diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/PolyLine.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/PolyLine.kt index 0f366dbe..1681f44a 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/PolyLine.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/PolyLine.kt @@ -3,11 +3,14 @@ package space.kscience.visionforge.solid import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import space.kscience.dataforge.meta.number -import space.kscience.visionforge.* +import space.kscience.visionforge.MutableVisionContainer +import space.kscience.visionforge.VisionBuilder +import space.kscience.visionforge.root +import space.kscience.visionforge.setChild @Serializable @SerialName("solid.line") -public class PolyLine(public val points: List) : SolidBase() { +public class PolyLine(public val points: List) : SolidBase() { //var lineType by string() public var thickness: Number by properties.root(inherit = false, includeStyles = true).number { DEFAULT_THICKNESS } @@ -19,7 +22,7 @@ public class PolyLine(public val points: List) : SolidBase() @VisionBuilder public fun MutableVisionContainer.polyline( - vararg points: Point3D, + vararg points: Float32Vector3D, name: String? = null, action: PolyLine.() -> Unit = {}, ): PolyLine = PolyLine(points.toList()).apply(action).also { setChild(name, it) } \ No newline at end of file diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Solid.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Solid.kt index f99e4bbf..f33573d7 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Solid.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Solid.kt @@ -8,6 +8,9 @@ import space.kscience.dataforge.meta.descriptors.value 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.visionforge.* import space.kscience.visionforge.Vision.Companion.VISIBLE_KEY import space.kscience.visionforge.solid.Solid.Companion.DETAIL_KEY @@ -58,8 +61,6 @@ public interface Solid : Vision { public val ROTATION_KEY: Name = "rotation".asName() - public val QUATERNION_KEY: Name = "quaternion".asName() - public val X_ROTATION_KEY: Name = ROTATION_KEY + X_KEY public val Y_ROTATION_KEY: Name = ROTATION_KEY + Y_KEY public val Z_ROTATION_KEY: Name = ROTATION_KEY + Z_KEY @@ -122,15 +123,6 @@ public var Solid.layer: Int // Common properties -public enum class RotationOrder { - XYZ, - YZX, - ZXY, - XZY, - YXZ, - ZYX -} - /** * Rotation order */ @@ -174,18 +166,21 @@ internal fun point( defaultX: Float, defaultY: Float = defaultX, defaultZ: Float = defaultX, -): ReadWriteProperty = - object : ReadWriteProperty { - override fun getValue(thisRef: Solid, property: KProperty<*>): Point3D? { +): ReadWriteProperty = + object : ReadWriteProperty { + override fun getValue(thisRef: Solid, property: KProperty<*>): Float32Vector3D? { val item = thisRef.properties.own?.get(name) ?: return null - return object : Point3D { + //using dynamic property accessor because values could change + return object : Float32Vector3D { override val x: Float get() = item[X_KEY]?.float ?: defaultX override val y: Float get() = item[Y_KEY]?.float ?: defaultY override val z: Float get() = item[Z_KEY]?.float ?: defaultZ + + override fun toString(): String = item.toString() } } - override fun setValue(thisRef: Solid, property: KProperty<*>, value: Point3D?) { + override fun setValue(thisRef: Solid, property: KProperty<*>, value: Float32Vector3D?) { if (value == null) { thisRef.properties.setProperty(name, null) } else { @@ -196,9 +191,9 @@ internal fun point( } } -public var Solid.position: Point3D? by point(POSITION_KEY, 0f) -public var Solid.rotation: Point3D? by point(ROTATION_KEY, 0f) -public var Solid.scale: Point3D? by point(SCALE_KEY, 1f) +public var Solid.position: Float32Vector3D? by point(POSITION_KEY, 0f) +public var Solid.rotation: Float32Vector3D? by point(ROTATION_KEY, 0f) +public var Solid.scale: Float32Vector3D? by point(SCALE_KEY, 1f) public var Solid.x: Number by float(X_POSITION_KEY, 0f) public var Solid.y: Number by float(Y_POSITION_KEY, 0f) @@ -208,33 +203,49 @@ public var Solid.rotationX: Number by float(X_ROTATION_KEY, 0f) public var Solid.rotationY: Number by float(Y_ROTATION_KEY, 0f) public var Solid.rotationZ: Number by float(Z_ROTATION_KEY, 0f) -public var Solid.quaternion: Pair? - get() = properties.getValue(Solid.QUATERNION_KEY)?.list?.let { +/** + * Raw quaternion value defined in properties + */ +public var Solid.quaternionOrNull: Quaternion? + get() = properties.getValue(ROTATION_KEY)?.list?.let { require(it.size == 4) { "Quaternion must be a number array of 4 elements" } - it[0].float to Point3D(it[1].float, it[2].float, it[3].float) + Quaternion(it[0].float, it[1].float, it[2].float, it[3].float) } set(value) { properties.setValue( - Solid.QUATERNION_KEY, + ROTATION_KEY, value?.let { ListValue( - value.first, - value.second.x, - value.second.y, - value.second.z + value.w, + value.x, + value.y, + value.z ) } ) } - -//public var Solid.quaternion: Quaternion? -// get() = meta[Solid::quaternion.name]?.value?.doubleArray?.let { Quaternion(it) } -// set(value) { -// meta[Solid::quaternion.name] = value?.values?.asValue() -// } - +/** + * Quaternion value including information from euler angles + */ +public var Solid.quaternion: Quaternion + get() = quaternionOrNull ?: Quaternion.fromEuler( + rotationX.radians, + rotationY.radians, + rotationZ.radians, + rotationOrder + ) + set(value) { + quaternionOrNull = value + } public var Solid.scaleX: Number by float(X_SCALE_KEY, 1f) public var Solid.scaleY: Number by float(Y_SCALE_KEY, 1f) -public var Solid.scaleZ: Number by float(Z_SCALE_KEY, 1f) \ No newline at end of file +public var Solid.scaleZ: Number by float(Z_SCALE_KEY, 1f) + +/** + * Add rotation with given [angle] relative to given [axis] + */ +public fun Solid.rotate(angle: Angle, axis: DoubleVector3D): Unit = with(QuaternionField) { + quaternion = Quaternion.fromRotation(angle, axis)*quaternion +} \ No newline at end of file diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidGroup.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidGroup.kt index f601a30e..d36057d3 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidGroup.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidGroup.kt @@ -17,7 +17,7 @@ import space.kscience.visionforge.VisionBuilder */ public interface PrototypeHolder { /** - * Build or update prototype tree + * Build or update the prototype tree */ @VisionBuilder public fun prototypes(builder: MutableVisionContainer.() -> Unit) @@ -43,6 +43,9 @@ public class SolidGroup : AbstractVisionGroup(), Solid, PrototypeHolder, Mutable it to value }.toMap() + /** + * Get a child solid with given relative [name] if it exists + */ public operator fun get(name: Name): Solid? = children.getChild(name) as? Solid private var prototypes: SolidGroup? @@ -84,6 +87,8 @@ public class SolidGroup : AbstractVisionGroup(), Solid, PrototypeHolder, Mutable } } +public operator fun SolidGroup.get(name:String): Solid? = get(name.parseAsName()) + @VisionBuilder public inline fun MutableVisionContainer.solidGroup( name: Name? = null, @@ -99,3 +104,8 @@ public inline fun MutableVisionContainer.solidGroup( name: String, action: SolidGroup.() -> Unit = {}, ): SolidGroup = solidGroup(name.parseAsName(), action) + +/** + * Create a [SolidGroup] using given configuration [block] + */ +public inline fun SolidGroup(block: SolidGroup.() -> Unit): SolidGroup = SolidGroup().apply(block) \ No newline at end of file diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidReference.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidReference.kt index 80db0b3e..2770a7aa 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidReference.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidReference.kt @@ -2,7 +2,9 @@ package space.kscience.visionforge.solid import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -61,7 +63,7 @@ public class SolidReference( } override fun getValue(name: Name, inherit: Boolean?, includeStyles: Boolean?): Value? { - if(name == Vision.STYLE_KEY){ + if (name == Vision.STYLE_KEY) { return buildList { properties?.getValue(Vision.STYLE_KEY)?.list?.forEach { add(it) @@ -90,7 +92,7 @@ public class SolidReference( prototype.getStyleProperty(name)?.value?.let { return it } } - if(inheritFlag){ + if (inheritFlag) { //5. own inheritance parent?.properties?.getValue(name, inheritFlag, includeStyles)?.let { return it } //6. prototype inheritance @@ -226,13 +228,13 @@ internal class SolidReferenceChild( */ public fun MutableVisionContainer.ref( templateName: Name, - name: String? = null, + name: Name? = null, ): SolidReference = SolidReference(templateName).also { setChild(name, it) } public fun MutableVisionContainer.ref( - templateName: String, - name: String? = null, -): SolidReference = ref(Name.parse(templateName), name) + templateName: Name, + name: String, +): SolidReference = ref(templateName, name.parseAsName()) /** * Add new [SolidReference] wrapping given object and automatically adding it to the prototypes. @@ -251,7 +253,7 @@ public fun SolidGroup.newRef( } else if (existing != obj) { error("Can't add different prototype on top of existing one") } - return children.ref(prototypeName, name) + return children.ref(prototypeName, name?.parseAsName()) } diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Solids.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Solids.kt index 4900fc1f..1477c2a0 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Solids.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Solids.kt @@ -48,9 +48,12 @@ public class Solids(meta: Meta) : VisionPlugin(meta), MutableVisionContainer Unit, block: SolidGroup.() -> Unit): SolidGroup = + solid(Canvas3DOptions(options), block) diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Sphere.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Sphere.kt index ed43f663..224e8bba 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Sphere.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Sphere.kt @@ -20,12 +20,12 @@ public class Sphere( ) : SolidBase(), GeometrySolid { override fun toGeometry(geometryBuilder: GeometryBuilder) { - fun point3dFromSphCoord(r: Float, theta: Float, phi: Float): Point3D { + fun point3dFromSphCoord(r: Float, theta: Float, phi: Float): Float32Vector3D { // This transformation matches three.js sphere implementation val y = r * cos(theta) val z = r * sin(theta) * sin(phi) val x = -r * sin(theta) * cos(phi) - return Point3D(x, y, z) + return Float32Vector3D(x, y, z) } val segments = this.detail ?: 32 diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SphereLayer.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SphereLayer.kt index bb630772..fa025df2 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SphereLayer.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SphereLayer.kt @@ -27,12 +27,12 @@ public class SphereLayer( require(outerRadius > 0) { "Outer radius must be positive" } require(innerRadius >= 0) { "inner radius must be non-negative" } - fun point3dFromSphCoord(r: Float, theta: Float, phi: Float): Point3D { + fun point3dFromSphCoord(r: Float, theta: Float, phi: Float): Float32Vector3D { // This transformation matches three.js sphere implementation val y = r * cos(theta) val z = r * sin(theta) * sin(phi) val x = -r * sin(theta) * cos(phi) - return Point3D(x, y, z) + return Float32Vector3D(x, y, z) } val segments = detail ?: 32 diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/StlSolid.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/StlSolid.kt new file mode 100644 index 00000000..b9d9d835 --- /dev/null +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/StlSolid.kt @@ -0,0 +1,25 @@ +package space.kscience.visionforge.solid + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import space.kscience.visionforge.MutableVisionContainer +import space.kscience.visionforge.VisionBuilder +import space.kscience.visionforge.setChild + + +public sealed class StlSolid: SolidBase() + +@Serializable +@SerialName("solid.stl.url") +public class StlUrlSolid(public val url: String) : StlSolid() + +@Serializable +@SerialName("solid.stl.binary") +public class StlBinarySolid(public val data: ByteArray) : StlSolid() + +@VisionBuilder +public inline fun MutableVisionContainer.stl( + url: String, + name: String? = null, + action: StlSolid.() -> Unit = {}, +): StlSolid = StlUrlSolid(url).apply(action).also { setChild(name, it) } \ No newline at end of file diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/geometry.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/geometry.kt index e9c9c146..58c3021d 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/geometry.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/geometry.kt @@ -1,10 +1,5 @@ package space.kscience.visionforge.solid -import kotlinx.serialization.KSerializer -import kotlinx.serialization.Serializable -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.MetaProvider import space.kscience.dataforge.meta.float @@ -13,105 +8,48 @@ 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 import kotlin.math.PI -import kotlin.math.pow -import kotlin.math.sqrt public const val PI2: Float = 2 * PI.toFloat() -@Serializable -public data class Point2D(public var x: Float, public var y: Float) - -public fun Point2D(x: Number, y: Number): Point2D = Point2D(x.toFloat(), y.toFloat()) - -public fun Point2D.toMeta(): Meta = Meta { +public fun Float32Vector2D.toMeta(): Meta = Meta { X_KEY put x Y_KEY put y } -internal fun Meta.point2D(): Point2D = Point2D(this["x"].float ?: 0f, this["y"].float ?: 0f) +internal fun Meta.toVector2D(): Float32Vector2D = + Float32Vector2D(this["x"].float ?: 0f, this["y"].float ?: 0f) -@Serializable(Point3DSerializer::class) -public interface Point3D { - public val x: Float - public val y: Float - public val z: Float +//@Suppress("SERIALIZER_TYPE_INCOMPATIBLE") +//@Serializable(Point3DSerializer::class) +//public interface MutablePoint3D : Float32Vector3D { +// override var x: Float +// override var y: Float +// override var z: Float +//} +// +// +//public fun MutablePoint3D.normalizeInPlace() { +// val norm = sqrt(x.pow(2) + y.pow(2) + z.pow(2)) +// x /= norm +// y /= norm +// z /= norm +//} - public companion object { - public val ZERO: Point3D = Point3D(0.0, 0.0, 0.0) - public val ONE: Point3D = Point3D(1.0, 1.0, 1.0) - } -} - -@Suppress("SERIALIZER_TYPE_INCOMPATIBLE") -@Serializable(Point3DSerializer::class) -public interface MutablePoint3D : Point3D { - override var x: Float - override var y: Float - override var z: Float -} - -@Serializable -private class Point3DImpl(override var x: Float, override var y: Float, override var z: Float) : MutablePoint3D - -internal object Point3DSerializer : KSerializer { - - override val descriptor: SerialDescriptor = Point3DImpl.serializer().descriptor - - override fun deserialize(decoder: Decoder): MutablePoint3D = decoder.decodeSerializableValue(Point3DImpl.serializer()) - - override fun serialize(encoder: Encoder, value: Point3D) { - val impl: Point3DImpl = (value as? Point3DImpl) ?: Point3DImpl(value.x, value.y, value.z) - encoder.encodeSerializableValue(Point3DImpl.serializer(), impl) - } -} - -public fun Point3D(x: Number, y: Number, z: Number): Point3D = Point3DImpl(x.toFloat(), y.toFloat(), z.toFloat()) - -public operator fun Point3D.plus(other: Point3D): Point3D = Point3D( - this.x + other.x, - this.y + other.y, - this.z + other.z +internal fun MetaProvider.point3D(default: Float = 0f) = Float32Euclidean3DSpace.vector( + getMeta(X_KEY).float ?: default, + getMeta(Y_KEY).float ?: default, + getMeta(Z_KEY).float ?: default ) -public operator fun Point3D.minus(other: Point3D): Point3D = Point3D( - this.x - other.x, - this.y - other.y, - this.z - other.z -) -public operator fun Point3D.unaryMinus(): Point3D = Point3D( - -x, - -y, - -z -) - -public infix fun Point3D.cross(other: Point3D): Point3D = Point3D( - y * other.z - z * other.y, - z * other.x - x * other.z, - x * other.y - y * other.x -) - -public fun MutablePoint3D.normalizeInPlace() { - val norm = sqrt(x.pow(2) + y.pow(2) + z.pow(2)) - x /= norm - y /= norm - z /= norm -} - -internal fun MetaProvider.point3D(default: Float = 0f) = object : Point3D { - override val x: Float by float(default) - override val y: Float by float(default) - override val z: Float by float(default) -} - -public fun Point3D.toMeta(): Meta = Meta { +public fun Float32Vector3D.toMeta(): Meta = Meta { X_KEY put x Y_KEY put y Z_KEY put z } -internal fun Meta.toVector(default: Float = 0f) = Point3D( +internal fun Meta.toVector3D(default: Float = 0f) = Float32Vector3D( this[X_KEY].float ?: default, this[Y_KEY].float ?: default, this[Z_KEY].float ?: default diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/AxesScheme.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/AxesScheme.kt index d4eb8e2b..0ed9126e 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/AxesScheme.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/AxesScheme.kt @@ -7,11 +7,13 @@ import space.kscience.dataforge.meta.descriptors.MetaDescriptor import space.kscience.dataforge.meta.descriptors.value import space.kscience.dataforge.meta.double +@Deprecated("Use separate axes object instead") public class AxesScheme : Scheme() { public var visible: Boolean by boolean(false) public var size: Double by double(AXIS_SIZE) public var width: Double by double(AXIS_WIDTH) + @Suppress("DEPRECATION") public companion object : SchemeSpec(::AxesScheme) { public const val AXIS_SIZE: Double = 1000.0 public const val AXIS_WIDTH: Double = 3.0 diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/Canvas3DOptions.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/Canvas3DOptions.kt index 78e35dfa..b24c9b75 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/Canvas3DOptions.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/Canvas3DOptions.kt @@ -59,6 +59,7 @@ public class CanvasSize : Scheme() { } public class Canvas3DOptions : Scheme() { + @Suppress("DEPRECATION") public var axes: AxesScheme by spec(AxesScheme) public var camera: CameraScheme by spec(CameraScheme) public var controls: ControlsScheme by spec(ControlsScheme) @@ -75,6 +76,7 @@ public class Canvas3DOptions : Scheme() { public companion object : SchemeSpec(::Canvas3DOptions) { override val descriptor: MetaDescriptor by lazy { MetaDescriptor { + @Suppress("DEPRECATION") scheme(Canvas3DOptions::axes, AxesScheme) value(Canvas3DOptions::layers) { diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/transform/RemoveSingleChild.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/transform/RemoveSingleChild.kt index 6a309d94..d2667e76 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/transform/RemoveSingleChild.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/transform/RemoveSingleChild.kt @@ -3,6 +3,7 @@ package space.kscience.visionforge.solid.transform import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.asName +import space.kscience.kmath.complex.QuaternionField import space.kscience.visionforge.root import space.kscience.visionforge.solid.* @@ -14,10 +15,7 @@ internal fun Solid.updateFrom(other: Solid): Solid { x += other.x y += other.y z += other.y - if(quaternion != null || other.quaternion != null) TODO("Quaternion support not implemented") - rotationX += other.rotationX - rotationY += other.rotationY - rotationZ += other.rotationZ + quaternion = with(QuaternionField) { other.quaternion * quaternion } scaleX *= other.scaleX scaleY *= other.scaleY scaleZ *= other.scaleZ diff --git a/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/CompositeTest.kt b/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/CompositeTest.kt index e426aee2..a7c1c688 100644 --- a/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/CompositeTest.kt +++ b/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/CompositeTest.kt @@ -18,7 +18,7 @@ class CompositeTest { detail = 32 } material { - color.set("pink") + color("pink") } } } diff --git a/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/ConvexTest.kt b/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/ConvexTest.kt index 79274e62..8d838f6b 100644 --- a/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/ConvexTest.kt +++ b/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/ConvexTest.kt @@ -2,13 +2,10 @@ package space.kscience.visionforge.solid import space.kscience.dataforge.meta.getIndexed import space.kscience.dataforge.meta.toMeta -import space.kscience.dataforge.misc.DFExperimental import kotlin.test.Test import kotlin.test.assertEquals class ConvexTest { - @OptIn(DFExperimental::class) - @Suppress("UNUSED_VARIABLE") @Test fun testConvexBuilder() { val group = testSolids.solidGroup { diff --git a/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/GroupTest.kt b/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/GroupTest.kt index 7f5138b1..78e2f03c 100644 --- a/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/GroupTest.kt +++ b/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/GroupTest.kt @@ -18,7 +18,7 @@ class GroupTest { } box(100, 100, 100) material { - color.set(Colors.lightgreen) + color(Colors.lightgreen) opacity = 0.3f } } @@ -30,7 +30,7 @@ class GroupTest { } box(100, 100, 100) y = 300 - color.set(Colors.red) + color(Colors.red) } subtract("subtract") { box(100, 100, 100) { @@ -40,7 +40,7 @@ class GroupTest { } box(100, 100, 100) y = -300 - color.set(Colors.blue) + color(Colors.blue) } } diff --git a/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/SerializationTest.kt b/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/SerializationTest.kt index 2497b5b3..e3069647 100644 --- a/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/SerializationTest.kt +++ b/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/SerializationTest.kt @@ -24,7 +24,7 @@ class SerializationTest { @Test fun testCubeSerialization() { val cube = Box(100f, 100f, 100f).apply { - color.set(222) + color(222) x = 100 z = -100 } @@ -37,7 +37,7 @@ class SerializationTest { @Test fun testProxySerialization() { val cube = Box(100f, 100f, 100f).apply { - color.set(222) + color(222) x = 100 z = -100 } @@ -59,7 +59,7 @@ class SerializationTest { fun lightSerialization(){ val group = testSolids.solidGroup { ambientLight { - color.set(Colors.white) + color(Colors.white) intensity = 100.0 } } diff --git a/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/SolidPropertyTest.kt b/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/SolidPropertyTest.kt index 5fa22b86..87ba368c 100644 --- a/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/SolidPropertyTest.kt +++ b/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/SolidPropertyTest.kt @@ -20,7 +20,7 @@ class SolidPropertyTest { val box = Box(10.0f, 10.0f, 10.0f) box.material { //meta["color"] = "pink" - color.set("pink") + color("pink") } assertEquals("pink", box.properties.getValue("material.color")?.string) assertEquals("pink", box.color.string) @@ -41,7 +41,7 @@ class SolidPropertyTest { delay(5) box.material { - color.set("pink") + color("pink") } assertEquals("pink", c.await()) diff --git a/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/SolidReferenceTest.kt b/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/SolidReferenceTest.kt index 8e851cb5..d8d971bb 100644 --- a/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/SolidReferenceTest.kt +++ b/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/SolidReferenceTest.kt @@ -16,7 +16,7 @@ class SolidReferenceTest { SolidMaterial.MATERIAL_COLOR_KEY put "red" } newRef("test", Box(100f,100f,100f).apply { - color.set("blue") + color("blue") useStyle(theStyle) }) } diff --git a/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/VisionUpdateTest.kt b/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/VisionUpdateTest.kt index 50e9362d..0e495aaa 100644 --- a/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/VisionUpdateTest.kt +++ b/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/VisionUpdateTest.kt @@ -22,7 +22,7 @@ internal class VisionUpdateTest { } val dif = visionManager.VisionChange { solidGroup("top") { - color.set(123) + color(123) box(100, 100, 100) } propertyChanged("top".asName(), SolidMaterial.MATERIAL_COLOR_KEY, Meta("red".asValue())) @@ -41,7 +41,7 @@ internal class VisionUpdateTest { fun testVisionChangeSerialization() { val change = visionManager.VisionChange { solidGroup("top") { - color.set(123) + color(123) box(100, 100, 100) } propertyChanged("top".asName(), SolidMaterial.MATERIAL_COLOR_KEY, Meta("red".asValue())) diff --git a/visionforge-tables/build.gradle.kts b/visionforge-tables/build.gradle.kts index 23d77912..cf813e6e 100644 --- a/visionforge-tables/build.gradle.kts +++ b/visionforge-tables/build.gradle.kts @@ -9,9 +9,9 @@ kscience { js { useCommonJs() binaries.library() - browser{ - commonWebpackConfig{ - cssSupport{ + browser { + commonWebpackConfig { + cssSupport { enabled.set(true) } } @@ -21,13 +21,13 @@ kscience { api(projects.visionforgeCore) api("space.kscience:tables-kt:${tablesVersion}") } - dependencies(jsMain){ - implementation(npm("tabulator-tables", "5.0.1")) - implementation(npm("@types/tabulator-tables", "5.0.1")) + dependencies(jsMain) { + implementation(npm("tabulator-tables", "5.4.4")) + implementation(npm("@types/tabulator-tables", "5.4.8")) } useSerialization() } -readme{ +readme { maturity = space.kscience.gradle.Maturity.PROTOTYPE } \ No newline at end of file diff --git a/visionforge-tables/src/commonMain/kotlin/space/kscience/visionforge/tables/VisionOfTable.kt b/visionforge-tables/src/commonMain/kotlin/space/kscience/visionforge/tables/VisionOfTable.kt index 20332974..3e4f9da8 100644 --- a/visionforge-tables/src/commonMain/kotlin/space/kscience/visionforge/tables/VisionOfTable.kt +++ b/visionforge-tables/src/commonMain/kotlin/space/kscience/visionforge/tables/VisionOfTable.kt @@ -86,6 +86,11 @@ public fun Table.toVision(): VisionOfTable = toVision { (it ?: "").asVal @JvmName("numberTableToVision") public fun Table.toVision(): VisionOfTable = toVision { (it ?: Double.NaN).asValue() } +@JvmName("anyTableToVision") +public fun Table.toVision(): VisionOfTable = toVision { + (it as? Number)?.asValue() ?: it?.toString()?.asValue() ?: Null +} + @DFExperimental public inline fun VisionOutput.table( vararg headers: ColumnHeader, diff --git a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeAmbientLightFactory.kt b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeAmbientLightFactory.kt index 3f8e251c..f15f2c78 100644 --- a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeAmbientLightFactory.kt +++ b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeAmbientLightFactory.kt @@ -14,7 +14,7 @@ import kotlin.reflect.KClass public object ThreeAmbientLightFactory : ThreeFactory { override val type: KClass get() = AmbientLightSource::class - override fun build(three: ThreePlugin, vision: AmbientLightSource, observe: Boolean): AmbientLight { + override suspend fun build(three: ThreePlugin, vision: AmbientLightSource, observe: Boolean): AmbientLight { val res = AmbientLight().apply { color = vision.color.threeColor() ?: Color(0x404040) intensity = vision.intensity.toDouble() diff --git a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeAxesFactory.kt b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeAxesFactory.kt new file mode 100644 index 00000000..8af445b9 --- /dev/null +++ b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeAxesFactory.kt @@ -0,0 +1,22 @@ +package space.kscience.visionforge.solid.three + +import space.kscience.visionforge.onPropertyChange +import space.kscience.visionforge.solid.AxesSolid +import three.helpers.AxesHelper +import kotlin.reflect.KClass + +public object ThreeAxesFactory : ThreeFactory { + override val type: KClass get() = AxesSolid::class + + override suspend fun build(three: ThreePlugin, vision: AxesSolid, observe: Boolean): AxesHelper { + val res = AxesHelper(vision.size.toInt()) + + if (observe) { + vision.onPropertyChange(three.context) { propertyName -> + res.updateProperty(vision, propertyName) + } + } + + return res + } +} \ No newline at end of file diff --git a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeBoxFactory.kt b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeBoxFactory.kt index 21c52198..d6840f75 100644 --- a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeBoxFactory.kt +++ b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeBoxFactory.kt @@ -5,7 +5,7 @@ import space.kscience.visionforge.solid.detail import three.geometries.BoxGeometry public object ThreeBoxFactory : ThreeMeshFactory(Box::class) { - override fun buildGeometry(obj: Box): BoxGeometry = + override suspend fun buildGeometry(obj: Box): BoxGeometry = obj.detail?.let { detail -> BoxGeometry(obj.xSize, obj.ySize, obj.zSize, detail, detail, detail) } ?: BoxGeometry(obj.xSize, obj.ySize, obj.zSize) diff --git a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeCanvas.kt b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeCanvas.kt index 4e6935a8..c692b88d 100644 --- a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeCanvas.kt +++ b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeCanvas.kt @@ -1,6 +1,7 @@ package space.kscience.visionforge.solid.three import kotlinx.browser.window +import kotlinx.coroutines.launch import org.w3c.dom.Element import org.w3c.dom.HTMLCanvasElement import org.w3c.dom.Node @@ -56,16 +57,6 @@ public class ThreeCanvas( axesObject.name = AXES_NAME add(axesObject) } - -// //Set up light -// options.useProperty(Canvas3DOptions::light, this) { lightConfig -> -// //remove old light if present -// getObjectByName(LIGHT_NAME)?.let { remove(it) } -// //add new light -// val lightObject = buildLight(lightConfig) -// lightObject.name = LIGHT_NAME -// add(lightObject) -// } } @@ -109,7 +100,7 @@ public class ThreeCanvas( } /** - * Force camera aspect ration and renderer size recalculation + * Force camera aspect ratio and renderer size recalculation */ private fun updateSize() { val width = element.clientWidth @@ -201,7 +192,7 @@ public class ThreeCanvas( } /** - * Resolve full name of the object relative to the global root + * Resolve the full name of the object relative to the global root */ private fun Object3D.fullName(): Name { if (root == null) error("Can't resolve element name without the root") @@ -212,7 +203,7 @@ public class ThreeCanvas( } } - //find first non-static parent in this object ancestry + //find the first non-static parent in this object ancestry private tailrec fun Object3D.upTrace(): Object3D? = if (!name.startsWith("@")) this else parent?.upTrace() private fun pick(): Object3D? { @@ -264,11 +255,13 @@ public class ThreeCanvas( scene.findChild("@root".asName())?.let { scene.remove(it) } root?.dispose() } + three.context.launch { + val object3D = three.buildObject3D(vision) - val object3D = three.buildObject3D(vision) - object3D.name = "@root" - scene.add(object3D) - root = object3D + object3D.name = "@root" + scene.add(object3D) + root = object3D + } } private var selected: Object3D? = null diff --git a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeCanvasLabelFactory.kt b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeCanvasLabelFactory.kt index b9f2a7d2..93bf170b 100644 --- a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeCanvasLabelFactory.kt +++ b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeCanvasLabelFactory.kt @@ -22,7 +22,7 @@ import kotlin.reflect.KClass public object ThreeCanvasLabelFactory : ThreeFactory { override val type: KClass get() = SolidLabel::class - override fun build(three: ThreePlugin, vision: SolidLabel, observe: Boolean): Object3D { + override suspend fun build(three: ThreePlugin, vision: SolidLabel, observe: Boolean): Object3D { val canvas = document.createElement("canvas") as HTMLCanvasElement canvas.width = 200 canvas.height = 200 diff --git a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeCompositeFactory.kt b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeCompositeFactory.kt index fe40022d..e6e9641c 100644 --- a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeCompositeFactory.kt +++ b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeCompositeFactory.kt @@ -38,7 +38,7 @@ public class ThreeCompositeFactory(public val three: ThreePlugin) : ThreeFactory override val type: KClass get() = Composite::class - override fun build(three: ThreePlugin, vision: Composite, observe: Boolean): Mesh { + override suspend fun build(three: ThreePlugin, vision: Composite, observe: Boolean): Mesh { val first = three.buildObject3D(vision.first, observe).takeIfMesh() ?: error("First part of composite is not a mesh") val second = three.buildObject3D(vision.second, observe).takeIfMesh() diff --git a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeConeFactory.kt b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeConeFactory.kt index 6b2db845..bcc147c7 100644 --- a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeConeFactory.kt +++ b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeConeFactory.kt @@ -8,7 +8,7 @@ import kotlin.math.PI import kotlin.math.pow public object ThreeConeFactory : ThreeMeshFactory(ConeSegment::class) { - override fun buildGeometry(obj: ConeSegment): BufferGeometry { + override suspend fun buildGeometry(obj: ConeSegment): BufferGeometry { val cylinder = obj.detail?.let { val segments = it.toDouble().pow(0.5).toInt() CylinderGeometry( diff --git a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeConvexFactory.kt b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeConvexFactory.kt index 83115b13..b772c1bf 100644 --- a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeConvexFactory.kt +++ b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeConvexFactory.kt @@ -4,7 +4,7 @@ import space.kscience.visionforge.solid.Convex import three.external.geometries.ConvexBufferGeometry public object ThreeConvexFactory : ThreeMeshFactory(Convex::class) { - override fun buildGeometry(obj: Convex): ConvexBufferGeometry { + override suspend fun buildGeometry(obj: Convex): ConvexBufferGeometry { val vectors = obj.points.map { it.toVector() }.toTypedArray() return ConvexBufferGeometry(vectors) } diff --git a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeFactory.kt b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeFactory.kt index a0852c72..122a2c30 100644 --- a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeFactory.kt +++ b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeFactory.kt @@ -26,7 +26,7 @@ public interface ThreeFactory { * Build an [Object3D] from [vision]. * @param observe if false, does not observe the changes in [vision] after render (useful for statics). */ - public fun build(three: ThreePlugin, vision: T, observe: Boolean = true): Object3D + public suspend fun build(three: ThreePlugin, vision: T, observe: Boolean = true): Object3D public companion object { public const val TYPE: String = "threeFactory" @@ -37,27 +37,19 @@ public interface ThreeFactory { * Update position, rotation and visibility */ public fun Object3D.updatePosition(vision: Vision) { - visible = vision.visible ?: true +// visible = vision.visible ?: true if (vision is Solid) { position.set(vision.x, vision.y, vision.z) -// val quaternion = obj.quaternion -// -// if (quaternion != null) { -// val (x, y, z, w) = quaternion -// setRotationFromQuaternion(Quaternion(x, y, z, w)) -// } else { -// setRotationFromEuler( Euler(obj.rotationX, obj.rotationY, obj.rotationZ, obj.rotationOrder.name)) -// } - val quaternion = vision.quaternion + val quaternion = vision.quaternionOrNull if (quaternion != null) { setRotationFromQuaternion( Quaternion( - quaternion.second.x, - quaternion.second.y, - quaternion.second.z, - quaternion.first + quaternion.x, + quaternion.y, + quaternion.z, + quaternion.w ) ) } else { @@ -92,7 +84,7 @@ public fun Object3D.updateProperty(source: Vision, propertyName: Name) { * Generic factory for elements which provide inside geometry builder */ public object ThreeShapeFactory : ThreeMeshFactory(GeometrySolid::class) { - override fun buildGeometry(obj: GeometrySolid): BufferGeometry = ThreeGeometryBuilder().apply { + override suspend fun buildGeometry(obj: GeometrySolid): BufferGeometry = ThreeGeometryBuilder().apply { obj.toGeometry(this) }.build() } \ No newline at end of file diff --git a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeGeometryBuilder.kt b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeGeometryBuilder.kt index d97c354a..7a5f3c49 100644 --- a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeGeometryBuilder.kt +++ b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeGeometryBuilder.kt @@ -1,15 +1,14 @@ 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.visionforge.solid.GeometryBuilder -import space.kscience.visionforge.solid.Point3D -import space.kscience.visionforge.solid.cross -import space.kscience.visionforge.solid.minus import three.core.BufferGeometry import three.core.Float32BufferAttribute import three.math.Vector3 -internal fun Point3D.toVector() = Vector3(x, y, z) +internal fun Float32Vector3D.toVector() = Vector3(x, y, z) internal fun MutableList.add(vararg values: T) { values.forEach { @@ -27,10 +26,10 @@ public class ThreeGeometryBuilder : GeometryBuilder { private val normals = ArrayList() // private val colors = ArrayList() - private val vertexCache = HashMap() + private val vertexCache = HashMap() private var counter: Short = -1 - private fun vertex(vertex: Point3D, normal: Point3D): Short = vertexCache.getOrPut(vertex) { + private fun vertex(vertex: Float32Vector3D, normal: Float32Vector3D): Short = vertexCache.getOrPut(vertex) { //add vertex and update cache if needed positions.add(vertex.x, vertex.y, vertex.z) normals.add(normal.x, vertex.y, vertex.z) @@ -39,8 +38,14 @@ public class ThreeGeometryBuilder : GeometryBuilder { counter } - override fun face(vertex1: Point3D, vertex2: Point3D, vertex3: Point3D, normal: Point3D?, meta: Meta) { - val actualNormal: Point3D = normal ?: ((vertex3 - vertex2) cross (vertex1 - vertex2)) + override fun face( + vertex1: Float32Vector3D, + vertex2: Float32Vector3D, + vertex3: Float32Vector3D, + normal: Float32Vector3D?, + meta: Meta, + ) = with(Float32Euclidean3DSpace) { + val actualNormal: Float32Vector3D = normal ?: ((vertex3 - vertex2) cross (vertex1 - vertex2)) indices.add( vertex(vertex1, actualNormal), vertex(vertex2, actualNormal), diff --git a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeJsVision.kt b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeJsVision.kt index 9d4b8e86..6f6e166a 100644 --- a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeJsVision.kt +++ b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeJsVision.kt @@ -7,5 +7,5 @@ import three.core.Object3D * A custom visual object that has its own Three.js renderer */ public abstract class ThreeJsVision : SolidBase() { - public abstract fun render(three: ThreePlugin): Object3D + public abstract suspend fun render(three: ThreePlugin): Object3D } diff --git a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeLabelFactory.kt b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeLabelFactory.kt index e168c1d2..45068a3a 100644 --- a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeLabelFactory.kt +++ b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeLabelFactory.kt @@ -17,7 +17,7 @@ import kotlin.reflect.KClass public object ThreeLabelFactory : ThreeFactory { override val type: KClass get() = SolidLabel::class - override fun build(three: ThreePlugin, vision: SolidLabel, observe: Boolean): Object3D { + override suspend fun build(three: ThreePlugin, vision: SolidLabel, observe: Boolean): Object3D { val textGeo = TextBufferGeometry(vision.text, jso { font = vision.fontFamily size = 20 diff --git a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeLineFactory.kt b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeLineFactory.kt index edfd6b65..45325430 100644 --- a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeLineFactory.kt +++ b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeLineFactory.kt @@ -16,7 +16,7 @@ import kotlin.reflect.KClass public object ThreeLineFactory : ThreeFactory { override val type: KClass get() = PolyLine::class - override fun build(three: ThreePlugin, vision: PolyLine, observe: Boolean): Object3D { + override suspend fun build(three: ThreePlugin, vision: PolyLine, observe: Boolean): Object3D { val geometry = BufferGeometry().apply { setFromPoints(Array((vision.points.size - 1) * 2) { vision.points[ceil(it / 2.0).toInt()].toVector() diff --git a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeMeshFactory.kt b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeMeshFactory.kt index 30f3034b..06224bbb 100644 --- a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeMeshFactory.kt +++ b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeMeshFactory.kt @@ -21,12 +21,13 @@ import kotlin.reflect.KClass public abstract class ThreeMeshFactory( override val type: KClass, ) : ThreeFactory { + /** * Build a geometry for an object */ - public abstract fun buildGeometry(obj: T): BufferGeometry + public abstract suspend fun buildGeometry(obj: T): BufferGeometry - override fun build(three: ThreePlugin, vision: T, observe: Boolean): Mesh { + override suspend fun build(three: ThreePlugin, vision: T, observe: Boolean): Mesh { val geometry = buildGeometry(vision) val mesh = Mesh(geometry, ThreeMaterials.DEFAULT).apply { diff --git a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeMeshLineFactory.kt b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeMeshLineFactory.kt index 3d9b4c3b..4a94823b 100644 --- a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeMeshLineFactory.kt +++ b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeMeshLineFactory.kt @@ -15,7 +15,7 @@ import kotlin.reflect.KClass public object ThreeMeshLineFactory : ThreeFactory { override val type: KClass get() = PolyLine::class - override fun build(three: ThreePlugin, vision: PolyLine, observe: Boolean): Object3D { + override suspend fun build(three: ThreePlugin, vision: PolyLine, observe: Boolean): Object3D { val geometry = MeshLine( Array((vision.points.size - 1) * 2) { vision.points[ceil(it / 2.0).toInt()].toVector() diff --git a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreePlugin.kt b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreePlugin.kt index 8f577baa..c1c2cf4d 100644 --- a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreePlugin.kt +++ b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreePlugin.kt @@ -37,6 +37,8 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer { objectFactories[SolidLabel::class] = ThreeCanvasLabelFactory objectFactories[AmbientLightSource::class] = ThreeAmbientLightFactory objectFactories[PointLightSource::class] = ThreePointLightFactory + objectFactories[StlSolid::class] = ThreeStlFactory + objectFactories[AxesSolid::class] = ThreeAxesFactory } @Suppress("UNCHECKED_CAST") @@ -46,7 +48,7 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer { as ThreeFactory? } - public fun buildObject3D(vision: Solid, observe: Boolean = true): Object3D = when (vision) { + public suspend fun buildObject3D(vision: Solid, observe: Boolean = true): Object3D = when (vision) { is ThreeJsVision -> vision.render(this) is SolidReference -> ThreeReferenceFactory.build(this, vision, observe) is SolidGroup -> { diff --git a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreePointLightFactory.kt b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreePointLightFactory.kt index fdd2e019..b0a0bfeb 100644 --- a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreePointLightFactory.kt +++ b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreePointLightFactory.kt @@ -13,7 +13,7 @@ public object ThreePointLightFactory : ThreeFactory { private val DEFAULT_COLOR = Color(0x404040) - override fun build(three: ThreePlugin, vision: PointLightSource, observe: Boolean): PointLight { + override suspend fun build(three: ThreePlugin, vision: PointLightSource, observe: Boolean): PointLight { val res = PointLight().apply { matrixAutoUpdate = false color = vision.color.threeColor() ?: DEFAULT_COLOR diff --git a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeReferenceFactory.kt b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeReferenceFactory.kt index 405956f8..beb59df8 100644 --- a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeReferenceFactory.kt +++ b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeReferenceFactory.kt @@ -31,7 +31,7 @@ public object ThreeReferenceFactory : ThreeFactory { } } - override fun build(three: ThreePlugin, vision: SolidReference, observe: Boolean): Object3D { + override suspend fun build(three: ThreePlugin, vision: SolidReference, observe: Boolean): Object3D { val template = vision.prototype val cachedObject = cache.getOrPut(template) { three.buildObject3D(template) diff --git a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeSmartLineFactory.kt b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeSmartLineFactory.kt index 2e7329da..b3609b66 100644 --- a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeSmartLineFactory.kt +++ b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeSmartLineFactory.kt @@ -7,11 +7,13 @@ import kotlin.reflect.KClass public object ThreeSmartLineFactory : ThreeFactory { override val type: KClass get() = PolyLine::class - override fun build(three: ThreePlugin, vision: PolyLine, observe: Boolean): Object3D { - return if (vision.thickness == 1.0) { - ThreeLineFactory.build(three, vision, observe) - } else { - ThreeMeshLineFactory.build(three, vision, observe) - } + override suspend fun build( + three: ThreePlugin, + vision: PolyLine, + observe: Boolean, + ): Object3D = if (vision.thickness == 1.0) { + ThreeLineFactory.build(three, vision, observe) + } else { + ThreeMeshLineFactory.build(three, vision, observe) } } \ No newline at end of file diff --git a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeSphereFactory.kt b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeSphereFactory.kt index d738c073..9753f9ad 100644 --- a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeSphereFactory.kt +++ b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeSphereFactory.kt @@ -6,7 +6,7 @@ import three.core.BufferGeometry import three.geometries.SphereGeometry public object ThreeSphereFactory : ThreeMeshFactory(Sphere::class) { - override fun buildGeometry(obj: Sphere): BufferGeometry { + override suspend fun buildGeometry(obj: Sphere): BufferGeometry { return obj.detail?.let { detail -> SphereGeometry( radius = obj.radius, diff --git a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeStlFactory.kt b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeStlFactory.kt new file mode 100644 index 00000000..e3fb676f --- /dev/null +++ b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeStlFactory.kt @@ -0,0 +1,38 @@ +package space.kscience.visionforge.solid.three + +import org.khronos.webgl.ArrayBuffer +import org.khronos.webgl.Int8Array +import space.kscience.visionforge.solid.StlBinarySolid +import space.kscience.visionforge.solid.StlSolid +import space.kscience.visionforge.solid.StlUrlSolid +import three.core.BufferGeometry +import three.external.loaders.STLLoader +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine + +fun ArrayBuffer.toByteArray(): ByteArray = Int8Array(this).unsafeCast() + +public object ThreeStlFactory : ThreeMeshFactory(StlSolid::class) { + + private val loader = STLLoader().apply { + requestHeader = listOf("Access-Control-Allow-Origin: *") + } + + override suspend fun buildGeometry(obj: StlSolid): BufferGeometry = when (obj) { + is StlBinarySolid -> loader.parse(obj.data) + is StlUrlSolid -> suspendCoroutine { continuation -> + loader.load( + url = obj.url, + onLoad = { + continuation.resume(it) + }, + onError = { + continuation.resumeWithException(RuntimeException("Failed to load STL object from ${obj.url}")) + } + ) + } + } + + +} \ No newline at end of file diff --git a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/three.kt b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/three.kt index 9c9d5b2b..f3fc0594 100644 --- a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/three.kt +++ b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/three.kt @@ -12,14 +12,10 @@ import three.math.Vector3 import three.objects.Mesh import three.textures.Texture import kotlin.contracts.contract -import kotlin.math.PI public val Meta.vector: Vector3 get() = Vector3(this["x"].float ?: 0f, this["y"].float ?: 0f, this["z"].float ?: 0f) -internal fun Double.toRadians() = this * PI / 180 - - internal fun Any.dispose() { when (this) { is BufferGeometry -> dispose() diff --git a/visionforge-threejs/src/jsMain/kotlin/three/external/loaders/STLLoader.kt b/visionforge-threejs/src/jsMain/kotlin/three/external/loaders/STLLoader.kt index 5fa253ab..e3e6133f 100644 --- a/visionforge-threejs/src/jsMain/kotlin/three/external/loaders/STLLoader.kt +++ b/visionforge-threejs/src/jsMain/kotlin/three/external/loaders/STLLoader.kt @@ -22,17 +22,19 @@ * THE SOFTWARE. */ -@file:JsModule("three") +@file:JsModule("three/examples/jsm/loaders/STLLoader.js") @file:JsNonModule package three.external.loaders +import org.khronos.webgl.ArrayBuffer import org.w3c.xhr.XMLHttpRequest import three.core.BufferGeometry -import three.core.Object3D external class STLLoader { + var requestHeader: List + fun load( url: String, onLoad: (BufferGeometry) -> Unit, @@ -40,7 +42,10 @@ external class STLLoader { onError: () -> Unit = definedExternally ) - fun parse(data: String): Object3D - fun parse(data: ByteArray): Object3D + fun parse(data: String): BufferGeometry + + fun parse(data: ByteArray): BufferGeometry + + fun parse(data: ArrayBuffer): BufferGeometry } \ No newline at end of file diff --git a/visionforge-threejs/visionforge-threejs-server/src/jvmTest/kotlin/space/kscience/visionforge/three/TestServerExtensions.kt b/visionforge-threejs/visionforge-threejs-server/src/jvmTest/kotlin/space/kscience/visionforge/three/TestServerExtensions.kt index 99e7fba6..95d9e6f7 100644 --- a/visionforge-threejs/visionforge-threejs-server/src/jvmTest/kotlin/space/kscience/visionforge/three/TestServerExtensions.kt +++ b/visionforge-threejs/visionforge-threejs-server/src/jvmTest/kotlin/space/kscience/visionforge/three/TestServerExtensions.kt @@ -3,18 +3,20 @@ package space.kscience.visionforge.three import kotlinx.html.stream.createHTML import space.kscience.visionforge.html.ResourceLocation import space.kscience.visionforge.html.VisionPage +import space.kscience.visionforge.html.appendTo import space.kscience.visionforge.html.importScriptHeader import kotlin.test.Test class TestServerExtensions { + @Suppress("UNUSED_VARIABLE") @Test fun testServerHeader(){ val string = createHTML().apply { VisionPage.importScriptHeader( "js/visionforge-three.js", ResourceLocation.SYSTEM - ).invoke(this) + ).appendTo(this) }.finalize()