From 38302eac4c73e1de356c31c66bda3c80e6bd122a Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sat, 3 Jun 2023 17:55:27 +0300 Subject: [PATCH 01/22] Use KMath-geometry for solids --- .../kotlin/ru/mipt/npm/root/dRootToSolid.kt | 24 ++-- .../npm/root/serialization/rootToSolid.kt | 6 +- demo/js-playground/build.gradle.kts | 1 + demo/muon-monitor/build.gradle.kts | 2 + .../kotlin/ru/mipt/npm/muon/monitor/Event.kt | 4 +- .../kotlin/ru/mipt/npm/muon/monitor/Model.kt | 2 +- .../ru/mipt/npm/muon/monitor/Monitor.kt | 14 +-- .../ru/mipt/npm/muon/monitor/MMServer.kt | 7 +- .../ru/mipt/npm/muon/monitor/sim/line.kt | 8 +- .../ru/mipt/npm/muon/monitor/sim/monitor.kt | 2 +- demo/solid-showcase/build.gradle.kts | 2 + .../kscience/visionforge/solid/demo/demo.kt | 5 +- .../kscience/visionforge/gdml/gdmlLoader.kt | 16 +-- visionforge-solid/build.gradle.kts | 3 + .../kscience/visionforge/solid/ConeSegment.kt | 8 +- .../kscience/visionforge/solid/ConeSurface.kt | 8 +- .../kscience/visionforge/solid/Convex.kt | 6 +- .../kscience/visionforge/solid/Extruded.kt | 22 ++-- .../solid/Float32Euclidean2DSpace.kt | 71 +++++++++++ .../solid/Float32Euclidean3DSpace.kt | 113 ++++++++++++++++++ .../visionforge/solid/GeometryBuilder.kt | 16 +-- .../kscience/visionforge/solid/Hexagon.kt | 64 +++++----- .../kscience/visionforge/solid/LightSource.kt | 2 +- .../kscience/visionforge/solid/PolyLine.kt | 9 +- .../space/kscience/visionforge/solid/Solid.kt | 23 ++-- .../kscience/visionforge/solid/Solids.kt | 1 + .../kscience/visionforge/solid/Sphere.kt | 4 +- .../kscience/visionforge/solid/SphereLayer.kt | 4 +- .../kscience/visionforge/solid/geometry.kt | 110 ++++------------- .../kscience/visionforge/solid/ConvexTest.kt | 3 - .../solid/three/ThreeGeometryBuilder.kt | 25 ++-- .../visionforge/three/TestServerExtensions.kt | 1 + 32 files changed, 365 insertions(+), 221 deletions(-) create mode 100644 visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Float32Euclidean2DSpace.kt create mode 100644 visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Float32Euclidean3DSpace.kt 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..a2e1f70a 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 @@ -32,12 +32,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: DGeoMatrix?) { @@ -72,7 +72,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]) } } } @@ -248,14 +248,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 +264,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) } 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/js-playground/build.gradle.kts b/demo/js-playground/build.gradle.kts index 86935c51..603b921a 100644 --- a/demo/js-playground/build.gradle.kts +++ b/demo/js-playground/build.gradle.kts @@ -7,6 +7,7 @@ kscience{ } kotlin{ + explicitApi = null js(IR){ useCommonJs() browser { diff --git a/demo/muon-monitor/build.gradle.kts b/demo/muon-monitor/build.gradle.kts index 2ea4d0eb..73336f1b 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..82c20def 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 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/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/solid-showcase/build.gradle.kts b/demo/solid-showcase/build.gradle.kts index 78e7d163..8f1c370b 100644 --- a/demo/solid-showcase/build.gradle.kts +++ b/demo/solid-showcase/build.gradle.kts @@ -19,6 +19,8 @@ kscience { } } +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..5fdec43b 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 @@ -130,7 +130,10 @@ fun VisionLayout.showcase() { color.set(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) 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..e17248ad 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 @@ -248,14 +248,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/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/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..1a6487b9 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,19 +4,23 @@ 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) @@ -38,7 +42,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 +54,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 +98,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..beeb4eb3 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 @@ -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/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..8bd64336 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 @@ -174,18 +174,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 +199,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,10 +211,10 @@ 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? +public var Solid.quaternion: Pair? get() = properties.getValue(Solid.QUATERNION_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) + it[0].float to Float32Vector3D(it[1].float, it[2].float, it[3].float) } set(value) { properties.setValue( 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..ab9fde1e 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 @@ -51,6 +51,7 @@ public class Solids(meta: Meta) : VisionPlugin(meta), MutableVisionContainer(), 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/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/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-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeGeometryBuilder.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeGeometryBuilder.kt index e75e4847..7a5f3c49 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeGeometryBuilder.kt +++ b/visionforge-threejs/src/main/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 three.core.BufferGeometry import three.core.Float32BufferAttribute import three.math.Vector3 -import space.kscience.dataforge.meta.Meta -import space.kscience.visionforge.solid.GeometryBuilder -import space.kscience.visionforge.solid.Point3D -import space.kscience.visionforge.solid.cross -import space.kscience.visionforge.solid.minus -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/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..ef4eb39b 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 @@ -8,6 +8,7 @@ import kotlin.test.Test class TestServerExtensions { + @Suppress("UNUSED_VARIABLE") @Test fun testServerHeader(){ val string = createHTML().apply { From 442fcb6c5b91def1cab21f91d99f27befc9a5470 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sat, 3 Jun 2023 18:29:04 +0300 Subject: [PATCH 02/22] Use KMath-geometry for solids --- .../kscience/visionforge/gdml/gdmlLoader.kt | 1 + .../space/kscience/visionforge/solid/Solid.kt | 42 ++++++++++++------- .../solid/transform/RemoveSingleChild.kt | 6 +-- .../visionforge/solid/three/ThreeFactory.kt | 10 ++--- 4 files changed, 35 insertions(+), 24 deletions(-) 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 e17248ad..230af4b0 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.* 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 8bd64336..e0140fe4 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,10 @@ 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.geometry.RotationOrder +import space.kscience.kmath.geometry.fromEuler +import space.kscience.kmath.geometry.radians import space.kscience.visionforge.* import space.kscience.visionforge.Vision.Companion.VISIBLE_KEY import space.kscience.visionforge.solid.Solid.Companion.DETAIL_KEY @@ -122,15 +126,6 @@ public var Solid.layer: Int // Common properties -public enum class RotationOrder { - XYZ, - YZX, - ZXY, - XZY, - YXZ, - ZYX -} - /** * Rotation order */ @@ -211,25 +206,42 @@ 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? +/** + * Raw quaternion value defined in properties + */ +public var Solid.quaternionValue: Quaternion? get() = properties.getValue(Solid.QUATERNION_KEY)?.list?.let { require(it.size == 4) { "Quaternion must be a number array of 4 elements" } - it[0].float to Float32Vector3D(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, value?.let { ListValue( - value.first, - value.second.x, - value.second.y, - value.second.z + value.w, + value.x, + value.y, + value.z ) } ) } +/** + * Quaternion value including information from euler angles + */ +public var Solid.quaternion: Quaternion + get() = quaternionValue ?: Quaternion.fromEuler( + rotationX.radians, + rotationY.radians, + rotationZ.radians, + rotationOrder + ) + set(value) { + quaternionValue = value + } + //public var Solid.quaternion: Quaternion? // get() = meta[Solid::quaternion.name]?.value?.doubleArray?.let { Quaternion(it) } 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-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeFactory.kt index a0852c72..ec42a3c7 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeFactory.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeFactory.kt @@ -49,15 +49,15 @@ public fun Object3D.updatePosition(vision: Vision) { // } else { // setRotationFromEuler( Euler(obj.rotationX, obj.rotationY, obj.rotationZ, obj.rotationOrder.name)) // } - val quaternion = vision.quaternion + val quaternion = vision.quaternionValue 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 { From 8204eb63c3fd03b96462c4ea6d8126b34e2b0ad3 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sat, 3 Jun 2023 20:49:57 +0300 Subject: [PATCH 03/22] Make threeJS object generation suspend --- .../visionforge/solid/demo/VariableBox.kt | 2 +- .../solid/three/ThreeAmbientLightFactory.kt | 2 +- .../visionforge/solid/three/ThreeCanvas.kt | 12 +++++++----- .../solid/three/ThreeCanvasLabelFactory.kt | 2 +- .../solid/three/ThreeCompositeFactory.kt | 2 +- .../visionforge/solid/three/ThreeFactory.kt | 10 +--------- .../visionforge/solid/three/ThreeJsVision.kt | 17 +++++++++++++++-- .../solid/three/ThreeLabelFactory.kt | 2 +- .../visionforge/solid/three/ThreeLineFactory.kt | 10 +++++----- .../visionforge/solid/three/ThreeMeshFactory.kt | 2 +- .../solid/three/ThreeMeshLineFactory.kt | 2 +- .../visionforge/solid/three/ThreePlugin.kt | 3 +-- .../solid/three/ThreePointLightFactory.kt | 2 +- .../solid/three/ThreeReferenceFactory.kt | 2 +- .../solid/three/ThreeSmartLineFactory.kt | 2 +- .../kscience/visionforge/solid/three/three.kt | 4 ---- 16 files changed, 39 insertions(+), 37 deletions(-) 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/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeAmbientLightFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeAmbientLightFactory.kt index 3f8e251c..f15f2c78 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeAmbientLightFactory.kt +++ b/visionforge-threejs/src/main/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/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvas.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvas.kt index 4e6935a8..6ecdad78 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvas.kt +++ b/visionforge-threejs/src/main/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 @@ -264,11 +265,12 @@ public class ThreeCanvas( scene.findChild("@root".asName())?.let { scene.remove(it) } root?.dispose() } - - val object3D = three.buildObject3D(vision) - object3D.name = "@root" - scene.add(object3D) - root = object3D + three.context.launch { + val object3D = three.buildObject3D(vision) + object3D.name = "@root" + scene.add(object3D) + root = object3D + } } private var selected: Object3D? = null diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvasLabelFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvasLabelFactory.kt index b9f2a7d2..93bf170b 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvasLabelFactory.kt +++ b/visionforge-threejs/src/main/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/main/kotlin/space/kscience/visionforge/solid/three/ThreeCompositeFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCompositeFactory.kt index fe40022d..e6e9641c 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCompositeFactory.kt +++ b/visionforge-threejs/src/main/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/main/kotlin/space/kscience/visionforge/solid/three/ThreeFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeFactory.kt index ec42a3c7..7c87a0f3 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeFactory.kt +++ b/visionforge-threejs/src/main/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" @@ -41,14 +41,6 @@ public fun Object3D.updatePosition(vision: Vision) { 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.quaternionValue if (quaternion != null) { diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeJsVision.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeJsVision.kt index 3829698e..dcb328a1 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeJsVision.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeJsVision.kt @@ -1,11 +1,24 @@ package space.kscience.visionforge.solid.three -import three.core.Object3D +import org.w3c.dom.url.URL import space.kscience.visionforge.solid.SolidBase +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 +} + +public class ThreeStlVision(val url: URL): ThreeJsVision(){ + override suspend fun render(three: ThreePlugin): Object3D { +// suspendCoroutine { +// +// } +// STLLoader() + + TODO() + } + } diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeLabelFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeLabelFactory.kt index e168c1d2..45068a3a 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeLabelFactory.kt +++ b/visionforge-threejs/src/main/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/main/kotlin/space/kscience/visionforge/solid/three/ThreeLineFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeLineFactory.kt index adf4ce0d..45325430 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeLineFactory.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeLineFactory.kt @@ -1,22 +1,22 @@ package space.kscience.visionforge.solid.three -import three.core.BufferGeometry -import three.core.Object3D -import three.math.Color -import three.objects.LineSegments import space.kscience.visionforge.onPropertyChange import space.kscience.visionforge.solid.PolyLine import space.kscience.visionforge.solid.SolidMaterial import space.kscience.visionforge.solid.color import space.kscience.visionforge.solid.string import space.kscience.visionforge.solid.three.ThreeMaterials.DEFAULT_LINE_COLOR +import three.core.BufferGeometry +import three.core.Object3D +import three.math.Color +import three.objects.LineSegments import kotlin.math.ceil 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/main/kotlin/space/kscience/visionforge/solid/three/ThreeMeshFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeMeshFactory.kt index 30f3034b..ab067326 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeMeshFactory.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeMeshFactory.kt @@ -26,7 +26,7 @@ public abstract class ThreeMeshFactory( */ public abstract 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/main/kotlin/space/kscience/visionforge/solid/three/ThreeMeshLineFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeMeshLineFactory.kt index 3d9b4c3b..4a94823b 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeMeshLineFactory.kt +++ b/visionforge-threejs/src/main/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/main/kotlin/space/kscience/visionforge/solid/three/ThreePlugin.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreePlugin.kt index 064597e6..17249648 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreePlugin.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreePlugin.kt @@ -6,7 +6,6 @@ import org.w3c.dom.Element import org.w3c.dom.HTMLElement import space.kscience.dataforge.context.* import space.kscience.dataforge.meta.Meta -import space.kscience.dataforge.meta.update import space.kscience.dataforge.names.* import space.kscience.visionforge.ElementVisionRenderer import space.kscience.visionforge.Vision @@ -47,7 +46,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/main/kotlin/space/kscience/visionforge/solid/three/ThreePointLightFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreePointLightFactory.kt index fdd2e019..b0a0bfeb 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreePointLightFactory.kt +++ b/visionforge-threejs/src/main/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/main/kotlin/space/kscience/visionforge/solid/three/ThreeReferenceFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeReferenceFactory.kt index 405956f8..beb59df8 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeReferenceFactory.kt +++ b/visionforge-threejs/src/main/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/main/kotlin/space/kscience/visionforge/solid/three/ThreeSmartLineFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeSmartLineFactory.kt index 2e7329da..6f2ee1a5 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeSmartLineFactory.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeSmartLineFactory.kt @@ -7,7 +7,7 @@ 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 { + override suspend fun build(three: ThreePlugin, vision: PolyLine, observe: Boolean): Object3D { return if (vision.thickness == 1.0) { ThreeLineFactory.build(three, vision, observe) } else { diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/three.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/three.kt index 9c9d5b2b..f3fc0594 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/three.kt +++ b/visionforge-threejs/src/main/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() From 2b70afdb869a7f812762d133877fea9f65ea1a81 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 4 Jun 2023 20:58:03 +0300 Subject: [PATCH 04/22] Suspended renderers and property change listeners --- demo/js-playground/build.gradle.kts | 2 +- demo/solid-showcase/build.gradle.kts | 14 +++---- .../kscience/visionforge/solid/demo/demo.kt | 4 ++ .../space/kscience/visionforge/Vision.kt | 2 +- .../kscience/visionforge/solid/StlVision.kt | 25 ++++++++++++ .../solid/three/ThreeBoxFactory.kt | 2 +- .../solid/three/ThreeConeFactory.kt | 2 +- .../solid/three/ThreeConvexFactory.kt | 2 +- .../visionforge/solid/three/ThreeFactory.kt | 2 +- .../visionforge/solid/three/ThreeJsVision.kt | 13 ------- .../solid/three/ThreeMeshFactory.kt | 3 +- .../visionforge/solid/three/ThreePlugin.kt | 1 + .../solid/three/ThreeSphereFactory.kt | 2 +- .../solid/three/ThreeStlFactory.kt | 38 +++++++++++++++++++ .../three/external/loaders/STLLoader.kt | 13 +++++-- 15 files changed, 93 insertions(+), 32 deletions(-) create mode 100644 visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/StlVision.kt create mode 100644 visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeStlFactory.kt diff --git a/demo/js-playground/build.gradle.kts b/demo/js-playground/build.gradle.kts index 603b921a..94835588 100644 --- a/demo/js-playground/build.gradle.kts +++ b/demo/js-playground/build.gradle.kts @@ -8,7 +8,7 @@ kscience{ kotlin{ explicitApi = null - js(IR){ + js{ useCommonJs() browser { binaries.executable() diff --git a/demo/solid-showcase/build.gradle.kts b/demo/solid-showcase/build.gradle.kts index 8f1c370b..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) @@ -21,6 +21,6 @@ kscience { kotlin.explicitApi = null -application { - mainClass.set("space.kscience.visionforge.solid.demo.FXDemoAppKt") -} \ No newline at end of file +//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 5fdec43b..6f2cb61f 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 @@ -150,6 +150,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() { 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-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/StlVision.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/StlVision.kt new file mode 100644 index 00000000..8c975c47 --- /dev/null +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/StlVision.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 StlVision: SolidBase() + +@Serializable +@SerialName("solid.stl.url") +public class StlUrlVision(public val url: String) : StlVision() + +@Serializable +@SerialName("solid.stl.binary") +public class StlBinaryVision(public val data: ByteArray) : StlVision() + +@VisionBuilder +public inline fun MutableVisionContainer.stl( + url: String, + name: String? = null, + action: StlVision.() -> Unit = {}, +): StlVision = StlUrlVision(url).apply(action).also { setChild(name, it) } \ No newline at end of file diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeBoxFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeBoxFactory.kt index 21c52198..d6840f75 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeBoxFactory.kt +++ b/visionforge-threejs/src/main/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/main/kotlin/space/kscience/visionforge/solid/three/ThreeConeFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeConeFactory.kt index 6b2db845..bcc147c7 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeConeFactory.kt +++ b/visionforge-threejs/src/main/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/main/kotlin/space/kscience/visionforge/solid/three/ThreeConvexFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeConvexFactory.kt index 83115b13..b772c1bf 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeConvexFactory.kt +++ b/visionforge-threejs/src/main/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/main/kotlin/space/kscience/visionforge/solid/three/ThreeFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeFactory.kt index 7c87a0f3..dcf94717 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeFactory.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeFactory.kt @@ -84,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/main/kotlin/space/kscience/visionforge/solid/three/ThreeJsVision.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeJsVision.kt index dcb328a1..6f6e166a 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeJsVision.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeJsVision.kt @@ -1,6 +1,5 @@ package space.kscience.visionforge.solid.three -import org.w3c.dom.url.URL import space.kscience.visionforge.solid.SolidBase import three.core.Object3D @@ -10,15 +9,3 @@ import three.core.Object3D public abstract class ThreeJsVision : SolidBase() { public abstract suspend fun render(three: ThreePlugin): Object3D } - -public class ThreeStlVision(val url: URL): ThreeJsVision(){ - override suspend fun render(three: ThreePlugin): Object3D { -// suspendCoroutine { -// -// } -// STLLoader() - - TODO() - } - -} diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeMeshFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeMeshFactory.kt index ab067326..06224bbb 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeMeshFactory.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeMeshFactory.kt @@ -21,10 +21,11 @@ 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 suspend fun build(three: ThreePlugin, vision: T, observe: Boolean): Mesh { val geometry = buildGeometry(vision) diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreePlugin.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreePlugin.kt index 17249648..e2af8c30 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreePlugin.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreePlugin.kt @@ -37,6 +37,7 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer { objectFactories[SolidLabel::class] = ThreeCanvasLabelFactory objectFactories[AmbientLightSource::class] = ThreeAmbientLightFactory objectFactories[PointLightSource::class] = ThreePointLightFactory + objectFactories[StlVision::class] = ThreeStlFactory } @Suppress("UNCHECKED_CAST") diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeSphereFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeSphereFactory.kt index d738c073..9753f9ad 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeSphereFactory.kt +++ b/visionforge-threejs/src/main/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/main/kotlin/space/kscience/visionforge/solid/three/ThreeStlFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeStlFactory.kt new file mode 100644 index 00000000..d21389aa --- /dev/null +++ b/visionforge-threejs/src/main/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.StlBinaryVision +import space.kscience.visionforge.solid.StlUrlVision +import space.kscience.visionforge.solid.StlVision +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(StlVision::class) { + + private val loader = STLLoader().apply { + requestHeader = listOf("Access-Control-Allow-Origin: *") + } + + override suspend fun buildGeometry(obj: StlVision): BufferGeometry = when (obj) { + is StlBinaryVision -> loader.parse(obj.data) + is StlUrlVision -> 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/main/kotlin/three/external/loaders/STLLoader.kt b/visionforge-threejs/src/main/kotlin/three/external/loaders/STLLoader.kt index 5fa253ab..e3e6133f 100644 --- a/visionforge-threejs/src/main/kotlin/three/external/loaders/STLLoader.kt +++ b/visionforge-threejs/src/main/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 From 20851baaf521a9de883ae89deefad27c62854d5a Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Tue, 6 Jun 2023 18:43:18 +0300 Subject: [PATCH 05/22] Make Axes a separate object --- demo/playground/src/jvmMain/kotlin/axes.kt | 22 ++++++++++++++++ .../kscience/visionforge/solid/MiscSolid.kt | 26 +++++++++++++++++++ .../space/kscience/visionforge/solid/Solid.kt | 21 +++++++-------- .../kscience/visionforge/solid/Solids.kt | 2 ++ .../solid/{StlVision.kt => StlSolid.kt} | 10 +++---- .../solid/specifications/AxesScheme.kt | 2 ++ .../solid/specifications/Canvas3DOptions.kt | 2 ++ .../solid/three/ThreeAxesFactory.kt | 22 ++++++++++++++++ .../visionforge/solid/three/ThreeCanvas.kt | 17 +++--------- .../visionforge/solid/three/ThreeFactory.kt | 2 +- .../visionforge/solid/three/ThreePlugin.kt | 3 ++- .../solid/three/ThreeSmartLineFactory.kt | 14 +++++----- .../solid/three/ThreeStlFactory.kt | 14 +++++----- 13 files changed, 112 insertions(+), 45 deletions(-) create mode 100644 demo/playground/src/jvmMain/kotlin/axes.kt create mode 100644 visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/MiscSolid.kt rename visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/{StlVision.kt => StlSolid.kt} (61%) create mode 100644 visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeAxesFactory.kt 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/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/Solid.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Solid.kt index e0140fe4..cb0c90f0 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 @@ -9,9 +9,7 @@ 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.geometry.RotationOrder -import space.kscience.kmath.geometry.fromEuler -import space.kscience.kmath.geometry.radians +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 @@ -242,14 +240,13 @@ public var Solid.quaternion: Quaternion quaternionValue = value } - -//public var Solid.quaternion: Quaternion? -// get() = meta[Solid::quaternion.name]?.value?.doubleArray?.let { Quaternion(it) } -// set(value) { -// meta[Solid::quaternion.name] = value?.values?.asValue() -// } - - 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) { + quaternion = Quaternion.fromRotation(angle, axis) +} \ No newline at end of file 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 ab9fde1e..e9301f67 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,6 +48,8 @@ public class Solids(meta: Meta) : VisionPlugin(meta), MutableVisionContainer() +public sealed class StlSolid: SolidBase() @Serializable @SerialName("solid.stl.url") -public class StlUrlVision(public val url: String) : StlVision() +public class StlUrlSolid(public val url: String) : StlSolid() @Serializable @SerialName("solid.stl.binary") -public class StlBinaryVision(public val data: ByteArray) : StlVision() +public class StlBinarySolid(public val data: ByteArray) : StlSolid() @VisionBuilder public inline fun MutableVisionContainer.stl( url: String, name: String? = null, - action: StlVision.() -> Unit = {}, -): StlVision = StlUrlVision(url).apply(action).also { setChild(name, it) } \ No newline at end of file + 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/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-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeAxesFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeAxesFactory.kt new file mode 100644 index 00000000..8af445b9 --- /dev/null +++ b/visionforge-threejs/src/main/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/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvas.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvas.kt index 6ecdad78..c692b88d 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvas.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvas.kt @@ -57,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) -// } } @@ -110,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 @@ -202,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") @@ -213,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? { @@ -267,6 +257,7 @@ public class ThreeCanvas( } three.context.launch { val object3D = three.buildObject3D(vision) + object3D.name = "@root" scene.add(object3D) root = object3D diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeFactory.kt index dcf94717..46e3d41b 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeFactory.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeFactory.kt @@ -37,7 +37,7 @@ 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) diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreePlugin.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreePlugin.kt index e2af8c30..c1c2cf4d 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreePlugin.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreePlugin.kt @@ -37,7 +37,8 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer { objectFactories[SolidLabel::class] = ThreeCanvasLabelFactory objectFactories[AmbientLightSource::class] = ThreeAmbientLightFactory objectFactories[PointLightSource::class] = ThreePointLightFactory - objectFactories[StlVision::class] = ThreeStlFactory + objectFactories[StlSolid::class] = ThreeStlFactory + objectFactories[AxesSolid::class] = ThreeAxesFactory } @Suppress("UNCHECKED_CAST") diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeSmartLineFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeSmartLineFactory.kt index 6f2ee1a5..b3609b66 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeSmartLineFactory.kt +++ b/visionforge-threejs/src/main/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 suspend 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/main/kotlin/space/kscience/visionforge/solid/three/ThreeStlFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeStlFactory.kt index d21389aa..e3fb676f 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeStlFactory.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeStlFactory.kt @@ -2,9 +2,9 @@ package space.kscience.visionforge.solid.three import org.khronos.webgl.ArrayBuffer import org.khronos.webgl.Int8Array -import space.kscience.visionforge.solid.StlBinaryVision -import space.kscience.visionforge.solid.StlUrlVision -import space.kscience.visionforge.solid.StlVision +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 @@ -13,15 +13,15 @@ import kotlin.coroutines.suspendCoroutine fun ArrayBuffer.toByteArray(): ByteArray = Int8Array(this).unsafeCast() -public object ThreeStlFactory : ThreeMeshFactory(StlVision::class) { +public object ThreeStlFactory : ThreeMeshFactory(StlSolid::class) { private val loader = STLLoader().apply { requestHeader = listOf("Access-Control-Allow-Origin: *") } - override suspend fun buildGeometry(obj: StlVision): BufferGeometry = when (obj) { - is StlBinaryVision -> loader.parse(obj.data) - is StlUrlVision -> suspendCoroutine { continuation -> + 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 = { From 4bce9ad34cbeff8c4caa219ffcf892d737059b6d Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 7 Jun 2023 17:44:52 +0300 Subject: [PATCH 06/22] update version --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 30eea98f..8997eeda 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-10" } subprojects { From 07b54fde51391c4e68a520f101143e0553521e90 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 7 Jun 2023 19:43:15 +0300 Subject: [PATCH 07/22] Use quaternion as rotation value --- .../kotlin/space/kscience/visionforge/solid/Solid.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) 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 cb0c90f0..a24192a8 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 @@ -60,8 +60,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 @@ -208,13 +206,13 @@ public var Solid.rotationZ: Number by float(Z_ROTATION_KEY, 0f) * Raw quaternion value defined in properties */ public var Solid.quaternionValue: Quaternion? - get() = properties.getValue(Solid.QUATERNION_KEY)?.list?.let { + get() = properties.getValue(ROTATION_KEY)?.list?.let { require(it.size == 4) { "Quaternion must be a number array of 4 elements" } 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.w, From f2d7e20fd3258830aa84d7b19e75b399c6fd3ad9 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 7 Jun 2023 21:29:40 +0300 Subject: [PATCH 08/22] Add dynamic rotation example --- .../space/kscience/visionforge/solid/demo/demo.kt | 13 +++++++++++-- .../space/kscience/visionforge/solid/Solid.kt | 5 +++-- 2 files changed, 14 insertions(+), 4 deletions(-) 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 6f2cb61f..baee4c71 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 @@ -103,8 +105,15 @@ fun VisionLayout.showcase() { solidGroup { x = 200 rotationY = PI / 4 + axes(200) box(100, 100, 100) { - rotationZ = PI / 4 + rotate((PI / 4).radians, Euclidean3DSpace.zAxis) + GlobalScope.launch(Dispatchers.Main) { + while (isActive) { + delay(100) + rotate((PI/20).radians,Euclidean3DSpace.yAxis) + } + } color.set(Colors.red) } } @@ -151,7 +160,7 @@ fun VisionLayout.showcase() { } } - demo("STL", "STL loaded from URL"){ + demo("STL", "STL loaded from URL") { stl("https://ozeki.hu/attachments/116/Menger_sponge_sample.stl") } } 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 a24192a8..c58b849d 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 @@ -9,6 +9,7 @@ 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 @@ -245,6 +246,6 @@ 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) { - quaternion = Quaternion.fromRotation(angle, axis) +public fun Solid.rotate(angle: Angle, axis: DoubleVector3D) = with(QuaternionField) { + quaternion = Quaternion.fromRotation(angle, axis)*quaternion } \ No newline at end of file From 8611327cc4bc8711e5e6dd6723b20ee7f53398e3 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Thu, 8 Jun 2023 10:04:31 +0300 Subject: [PATCH 09/22] antenna example --- demo/playground/src/jvmMain/kotlin/antenna.kt | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 demo/playground/src/jvmMain/kotlin/antenna.kt diff --git a/demo/playground/src/jvmMain/kotlin/antenna.kt b/demo/playground/src/jvmMain/kotlin/antenna.kt new file mode 100644 index 00000000..5b92f46f --- /dev/null +++ b/demo/playground/src/jvmMain/kotlin/antenna.kt @@ -0,0 +1,43 @@ +package space.kscience.visionforge.examples + +import space.kscience.kmath.complex.Quaternion +import space.kscience.kmath.geometry.RotationOrder +import space.kscience.kmath.geometry.degrees +import space.kscience.kmath.geometry.fromEuler +import space.kscience.visionforge.html.ResourceLocation +import space.kscience.visionforge.solid.* +import kotlin.math.PI + +fun main() = makeVisionFile(resourceLocation = ResourceLocation.SYSTEM) { + + val direction = Quaternion.fromEuler( 45.degrees, 45.degrees, 0.degrees, RotationOrder.XYZ) + + vision("canvas") { + requirePlugin(Solids) + solid { + rotationX = -PI / 2 + rotationZ = PI + axes(200) + ambientLight() + cylinder(50, 5, name = "base") + solidGroup("frame") { + rotationY = PI/2 + rotationX = -PI/2 + rotationZ = -PI/2 + z = 60 + axes(200) + solidGroup("antenna") { + tube(40, 10, 30) + sphereLayer(100, 95, theta = PI / 6) { + z = 100 + rotationX = -PI / 2 + } + cylinder(5, 30) { + z = 15 + } + this.quaternion = direction + } + } + } + } +} \ No newline at end of file From 04b4a129461f54476530c4b5d8ffb20a2495b49c Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Thu, 22 Jun 2023 08:32:52 +0300 Subject: [PATCH 10/22] antenna example --- demo/playground/src/jvmMain/kotlin/antenna.kt | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/demo/playground/src/jvmMain/kotlin/antenna.kt b/demo/playground/src/jvmMain/kotlin/antenna.kt index 5b92f46f..cd711d11 100644 --- a/demo/playground/src/jvmMain/kotlin/antenna.kt +++ b/demo/playground/src/jvmMain/kotlin/antenna.kt @@ -1,16 +1,27 @@ package space.kscience.visionforge.examples import space.kscience.kmath.complex.Quaternion -import space.kscience.kmath.geometry.RotationOrder +import space.kscience.kmath.complex.QuaternionField +import space.kscience.kmath.geometry.Angle +import space.kscience.kmath.geometry.Euclidean3DSpace import space.kscience.kmath.geometry.degrees -import space.kscience.kmath.geometry.fromEuler +import space.kscience.kmath.geometry.fromRotation import space.kscience.visionforge.html.ResourceLocation import space.kscience.visionforge.solid.* import kotlin.math.PI fun main() = makeVisionFile(resourceLocation = ResourceLocation.SYSTEM) { - val direction = Quaternion.fromEuler( 45.degrees, 45.degrees, 0.degrees, RotationOrder.XYZ) + 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) + vision("canvas") { requirePlugin(Solids) @@ -21,9 +32,6 @@ fun main() = makeVisionFile(resourceLocation = ResourceLocation.SYSTEM) { ambientLight() cylinder(50, 5, name = "base") solidGroup("frame") { - rotationY = PI/2 - rotationX = -PI/2 - rotationZ = -PI/2 z = 60 axes(200) solidGroup("antenna") { @@ -35,7 +43,7 @@ fun main() = makeVisionFile(resourceLocation = ResourceLocation.SYSTEM) { cylinder(5, 30) { z = 15 } - this.quaternion = direction + quaternion = direction } } } From 8b25761dc6a37cd51413641e8374c85707497c53 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 19 Jul 2023 22:25:32 +0300 Subject: [PATCH 11/22] Refactor jupyter integratin --- build.gradle.kts | 2 +- demo/playground/build.gradle.kts | 5 +- demo/playground/notebooks/common-demo.ipynb | 104 ++++++++++++++++++ demo/playground/notebooks/dynamic-demo.ipynb | 50 +-------- .../src/jsMain/kotlin/playgroundMain.kt | 4 +- demo/sat-demo/build.gradle.kts | 6 +- jupyter/visionforge-jupyter-gdml/README.md | 32 ------ .../src/jsMain/kotlin/gdmlJupyter.kt | 12 -- .../src/jvmMain/kotlin/GdmlForJupyter.kt | 38 ------- .../webpack.config.d/01.ring.js | 3 - settings.gradle.kts | 4 +- .../kscience/visionforge/html/VisionPage.kt | 2 +- {jupyter => visionforge-jupyter}/README.md | 0 .../build.gradle.kts | 2 + .../src/jsMain/kotlin/VFNotebookClient.kt | 6 +- .../src/jvmMain/kotlin/VisionForge.kt | 22 ++-- .../jvmMain/kotlin/VisionForgeIntegration.kt | 19 +++- .../src/jvmMain/kotlin/forms.kt | 11 +- .../build.gradle.kts | 22 ++-- .../src/jsMain/kotlin/commonJupyter.kt | 17 +++ .../kotlin/JupyterCommonIntegration.kt | 40 ++++--- .../webpack.config.d/01.ring.js | 24 ++++ visionforge-server/build.gradle.kts | 2 +- .../visionforge/server/VisionServer.kt | 1 - .../space/kscience/visionforge/solid/Solid.kt | 2 +- .../kscience/visionforge/solid/SolidGroup.kt | 5 + visionforge-tables/build.gradle.kts | 14 +-- .../visionforge/tables/VisionOfTable.kt | 5 + 28 files changed, 264 insertions(+), 190 deletions(-) create mode 100644 demo/playground/notebooks/common-demo.ipynb delete mode 100644 jupyter/visionforge-jupyter-gdml/README.md delete mode 100644 jupyter/visionforge-jupyter-gdml/src/jsMain/kotlin/gdmlJupyter.kt delete mode 100644 jupyter/visionforge-jupyter-gdml/src/jvmMain/kotlin/GdmlForJupyter.kt delete mode 100644 jupyter/visionforge-jupyter-gdml/webpack.config.d/01.ring.js rename {jupyter => visionforge-jupyter}/README.md (100%) rename {jupyter => visionforge-jupyter}/build.gradle.kts (86%) rename jupyter/src/jsMain/kotlin/VFNotebookPlugin.kt => visionforge-jupyter/src/jsMain/kotlin/VFNotebookClient.kt (90%) rename jupyter/src/jvmMain/kotlin/VFForNotebook.kt => visionforge-jupyter/src/jvmMain/kotlin/VisionForge.kt (88%) rename jupyter/src/jvmMain/kotlin/VFIntegrationBase.kt => visionforge-jupyter/src/jvmMain/kotlin/VisionForgeIntegration.kt (85%) rename {jupyter => visionforge-jupyter}/src/jvmMain/kotlin/forms.kt (82%) rename {jupyter/visionforge-jupyter-gdml => visionforge-jupyter/visionforge-jupyter-common}/build.gradle.kts (53%) create mode 100644 visionforge-jupyter/visionforge-jupyter-common/src/jsMain/kotlin/commonJupyter.kt rename demo/playground/src/jvmMain/kotlin/VisionForgePlayGroundForJupyter.kt => visionforge-jupyter/visionforge-jupyter-common/src/jvmMain/kotlin/JupyterCommonIntegration.kt (51%) create mode 100644 visionforge-jupyter/visionforge-jupyter-common/webpack.config.d/01.ring.js diff --git a/build.gradle.kts b/build.gradle.kts index 8997eeda..73f03bbd 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-10" + version = "0.3.0-dev-11" } subprojects { diff --git a/demo/playground/build.gradle.kts b/demo/playground/build.gradle.kts index ddc1d6a8..77dd565e 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) + implementation(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..73409ae7 --- /dev/null +++ b/demo/playground/notebooks/common-demo.ipynb @@ -0,0 +1,104 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "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-11\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + ":classpath" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "vf.startServer()" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "import space.kscience.visionforge.plotly.plotly\n", + "\n", + "vf.page {\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", + "}" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false + } + } + ], + "metadata": { + "kernelspec": { + "display_name": "Kotlin", + "language": "kotlin", + "name": "kotlin" + }, + "language_info": { + "name": "kotlin", + "version": "1.8.20", + "mimetype": "text/x-kotlin", + "file_extension": ".kt", + "pygments_lexer": "kotlin", + "codemirror_mode": "text/x-kotlin", + "nbconvert_exporter": "" + }, + "ktnbPluginMetadata": { + "isAddProjectLibrariesToClasspath": false + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} 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/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/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/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/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..f92454f0 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 @@ -26,7 +26,7 @@ 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 = { link { 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 86% rename from jupyter/build.gradle.kts rename to visionforge-jupyter/build.gradle.kts index 5808f287..ed4ab76d 100644 --- a/jupyter/build.gradle.kts +++ b/visionforge-jupyter/build.gradle.kts @@ -5,6 +5,7 @@ plugins { description = "Common visionforge jupyter module" kscience { + useKtor() jvm() js() jupyterLibrary() @@ -12,6 +13,7 @@ kscience { api(projects.visionforgeCore) } dependencies(jvmMain){ + api("io.ktor:ktor-server-cio") api(projects.visionforgeServer) } } diff --git a/jupyter/src/jsMain/kotlin/VFNotebookPlugin.kt b/visionforge-jupyter/src/jsMain/kotlin/VFNotebookClient.kt similarity index 90% rename from jupyter/src/jsMain/kotlin/VFNotebookPlugin.kt rename to visionforge-jupyter/src/jsMain/kotlin/VFNotebookClient.kt index f6a37f23..24304136 100644 --- a/jupyter/src/jsMain/kotlin/VFNotebookPlugin.kt +++ b/visionforge-jupyter/src/jsMain/kotlin/VFNotebookClient.kt @@ -13,7 +13,7 @@ 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) { @@ -39,8 +39,8 @@ public class VFNotebookPlugin : AbstractPlugin() { 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/jupyter/src/jvmMain/kotlin/VFForNotebook.kt b/visionforge-jupyter/src/jvmMain/kotlin/VisionForge.kt similarity index 88% rename from jupyter/src/jvmMain/kotlin/VFForNotebook.kt rename to visionforge-jupyter/src/jvmMain/kotlin/VisionForge.kt index 50619e44..0a538a3e 100644 --- a/jupyter/src/jvmMain/kotlin/VFForNotebook.kt +++ b/visionforge-jupyter/src/jvmMain/kotlin/VisionForge.kt @@ -16,9 +16,7 @@ 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.meta.* import space.kscience.dataforge.names.Name import space.kscience.visionforge.Vision import space.kscience.visionforge.VisionManager @@ -26,7 +24,6 @@ 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 @@ -41,9 +38,14 @@ internal fun TagConsumer<*>.renderScriptForId(id: String) { /** * A handler class that includes a server and common utilities */ -public class VFForNotebook(override val context: Context) : ContextAware, CoroutineScope { +public class VisionForge( + public val visionManager: VisionManager, + meta: Meta = Meta.EMPTY, +) : ContextAware, CoroutineScope { - public val visionManager: VisionManager = context.visionManager + override val context: Context get() = visionManager.context + + public val configuration: ObservableMutableMeta = meta.toMutableMeta() private var counter = 0 @@ -61,9 +63,11 @@ public class VFForNotebook(override val context: Context) : ContextAware, Corout public fun html(block: TagConsumer<*>.() -> Unit): MimeTypedResult = HTML(createHTML().apply(block).finalize()) + public fun getProperty(name: String): TypedMeta<*>? = configuration[name] ?: context.properties[name] + public fun startServer( - host: String = context.properties["visionforge.host"].string ?: "localhost", - port: Int = context.properties["visionforge.port"].int ?: VisionRoute.DEFAULT_PORT, + host: String = getProperty("visionforge.host").string ?: "localhost", + port: Int = getProperty("visionforge.port").int ?: VisionRoute.DEFAULT_PORT, ): MimeTypedResult = html { if (engine != null) { p { @@ -141,4 +145,4 @@ public class VFForNotebook(override val context: Context) : ContextAware, Corout 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/visionforge-jupyter/src/jvmMain/kotlin/VisionForgeIntegration.kt similarity index 85% rename from jupyter/src/jvmMain/kotlin/VFIntegrationBase.kt rename to visionforge-jupyter/src/jvmMain/kotlin/VisionForgeIntegration.kt index bf6322e9..3029ef32 100644 --- a/jupyter/src/jvmMain/kotlin/VFIntegrationBase.kt +++ b/visionforge-jupyter/src/jvmMain/kotlin/VisionForgeIntegration.kt @@ -18,12 +18,12 @@ import kotlin.random.nextUInt * A base class for different Jupyter VF integrations */ @DFExperimental -public abstract class VFIntegrationBase( +public abstract class VisionForgeIntegration( public val visionManager: VisionManager, ) : JupyterIntegration(), ContextAware { override val context: Context get() = visionManager.context - protected val handler: VFForNotebook = VFForNotebook(visionManager.context) + protected val handler: VisionForge = VisionForge(visionManager) protected abstract fun Builder.afterLoaded() @@ -33,13 +33,15 @@ public abstract class VFIntegrationBase( declare("VisionForge" to handler, "vf" to handler) } + onShutdown { handler.stopServer() } import( "kotlinx.html.*", - "space.kscience.visionforge.html.*" + "space.kscience.visionforge.html.*", + "space.kscience.visionforge.jupyter.*" ) render { fragment -> @@ -54,7 +56,6 @@ public abstract class VFIntegrationBase( handler.produceHtml { vision(vision) } - } render { page -> @@ -93,4 +94,12 @@ public abstract class VFIntegrationBase( afterLoaded() } -} \ No newline at end of file +} + +/** + * Create a standalone page in the notebook + */ +public fun VisionForge.page( + pageHeaders: Map = emptyMap(), + content: HtmlVisionFragment +): VisionPage = VisionPage(visionManager, pageHeaders, content) \ No newline at end of file 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/jupyter/visionforge-jupyter-gdml/build.gradle.kts b/visionforge-jupyter/visionforge-jupyter-common/build.gradle.kts similarity index 53% rename from jupyter/visionforge-jupyter-gdml/build.gradle.kts rename to visionforge-jupyter/visionforge-jupyter-common/build.gradle.kts index 5a67459e..c71b33d0 100644 --- a/jupyter/visionforge-jupyter-gdml/build.gradle.kts +++ b/visionforge-jupyter/visionforge-jupyter-common/build.gradle.kts @@ -2,10 +2,11 @@ plugins { id("space.kscience.gradle.mpp") } -description = "Jupyter api artifact for GDML rendering" +description = "Jupyter api artifact including all common modules" kscience { - fullStack("js/gdml-jupyter.js", + fullStack( + "js/visionforge-jupyter-common.js", jsConfig = { useCommonJs() } ) { commonWebpackConfig { @@ -16,21 +17,24 @@ kscience { } } - dependencies{ + dependencies { implementation(projects.visionforgeSolid) - implementation(projects.jupyter) + implementation(projects.visionforgePlotly) + implementation(projects.visionforgeTables) + implementation(projects.visionforgeMarkdown) + implementation(projects.visionforgeJupyter) } - dependencies(jvmMain){ + jvmMain { implementation(projects.visionforgeGdml) } - dependencies(jsMain){ - implementation(projects.visionforgeThreejs) + jsMain { implementation(projects.ui.ring) + implementation(projects.visionforgeThreejs) } - - jupyterLibrary("space.kscience.visionforge.gdml.jupyter.GdmlForJupyter") + + jupyterLibrary("space.kscience.visionforge.jupyter.JupyterCommonIntegration") } readme { 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/demo/playground/src/jvmMain/kotlin/VisionForgePlayGroundForJupyter.kt b/visionforge-jupyter/visionforge-jupyter-common/src/jvmMain/kotlin/JupyterCommonIntegration.kt similarity index 51% rename from demo/playground/src/jvmMain/kotlin/VisionForgePlayGroundForJupyter.kt rename to visionforge-jupyter/visionforge-jupyter-common/src/jvmMain/kotlin/JupyterCommonIntegration.kt index 0b87e06d..6bca7036 100644 --- a/demo/playground/src/jvmMain/kotlin/VisionForgePlayGroundForJupyter.kt +++ b/visionforge-jupyter/visionforge-jupyter-common/src/jvmMain/kotlin/JupyterCommonIntegration.kt @@ -1,46 +1,52 @@ -package space.kscience.visionforge.examples +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.tables.* import space.kscience.visionforge.gdml.toVision -import space.kscience.visionforge.jupyter.VFIntegrationBase +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 -internal class VisionForgePlayGroundForJupyter : VFIntegrationBase( - Context("VisionForge") { - plugin(Solids) - plugin(PlotlyPlugin) - }.visionManager -) { +public class JupyterCommonIntegration : VisionForgeIntegration(CONTEXT.visionManager) { override fun Builder.afterLoaded() { + resources { - js("VisionForge") { - classPath("js/visionforge-playground.js") + js("three") { + classPath("js/visionforge-jupyter-common.js") } } import( "space.kscience.gdml.*", - "space.kscience.plotly.*", - "space.kscience.plotly.models.*", "space.kscience.visionforge.solid.*", + "space.kscience.tables.*", + "space.kscience.dataforge.meta.*", ) - render { gdmlModel -> handler.produceHtml { vision { gdmlModel.toVision() } } } + render> { table -> + handler.produceHtml { + vision { table.toVision() } + } + } + render { plot -> handler.produceHtml { vision { plot.asVision() } @@ -48,4 +54,12 @@ internal class VisionForgePlayGroundForJupyter : VFIntegrationBase( } } + 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..00331969 100644 --- a/visionforge-server/build.gradle.kts +++ b/visionforge-server/build.gradle.kts @@ -6,7 +6,7 @@ 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") 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..47c5db84 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.* 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 c58b849d..8b719b54 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 @@ -246,6 +246,6 @@ 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) = with(QuaternionField) { +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..791bb0c9 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 @@ -99,3 +99,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-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, From c1f275ce450f4471494a02a6ef9f4b61da748221 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 19 Jul 2023 22:54:13 +0300 Subject: [PATCH 12/22] Work-around for strange dependency resolution in notebooks --- demo/playground/notebooks/common-demo.ipynb | 77 ++++++++++--------- .../build.gradle.kts | 12 +-- visionforge-server/build.gradle.kts | 2 +- 3 files changed, 48 insertions(+), 43 deletions(-) diff --git a/demo/playground/notebooks/common-demo.ipynb b/demo/playground/notebooks/common-demo.ipynb index 73409ae7..2c397a15 100644 --- a/demo/playground/notebooks/common-demo.ipynb +++ b/demo/playground/notebooks/common-demo.ipynb @@ -3,45 +3,50 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "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-11\")" + "@file:DependsOn(\"space.kscience:visionforge-jupyter-common-jvm:0.3.0-dev-11\")\n", + "@file:DependsOn(\"io.ktor:ktor-server-cio-jvm:2.3.0\")\n", + "@file:DependsOn(\"io.ktor:ktor-server-websockets-jvm:2.3.0\")\n", + "@file:DependsOn(\"io.ktor:ktor-server-cors-jvm:2.3.0\")" ] }, { "cell_type": "code", "execution_count": null, - "outputs": [], - "source": [ - ":classpath" - ], "metadata": { - "collapsed": false - } - }, - { - "cell_type": "code", - "execution_count": null, + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, "outputs": [], "source": [ + "import io.ktor.server.cio.CIO\n", + "\n", + "println(CIO)\n", + "\n", "vf.startServer()" - ], - "metadata": { - "collapsed": false - } + ] }, { "cell_type": "code", "execution_count": null, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, "outputs": [], "source": [ "import space.kscience.visionforge.plotly.plotly\n", + "import space.kscience.plotly.*\n", + "import space.kscience.plotly.models.*\n", "\n", "vf.page {\n", " h1 { +\"AAA\" }\n", @@ -65,19 +70,19 @@ " }\n", " }\n", "}" - ], - "metadata": { - "collapsed": false - } + ] }, { "cell_type": "code", "execution_count": null, - "outputs": [], - "source": [], "metadata": { - "collapsed": false - } + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [] } ], "metadata": { @@ -86,19 +91,19 @@ "language": "kotlin", "name": "kotlin" }, - "language_info": { - "name": "kotlin", - "version": "1.8.20", - "mimetype": "text/x-kotlin", - "file_extension": ".kt", - "pygments_lexer": "kotlin", - "codemirror_mode": "text/x-kotlin", - "nbconvert_exporter": "" - }, "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": 0 + "nbformat_minor": 4 } diff --git a/visionforge-jupyter/visionforge-jupyter-common/build.gradle.kts b/visionforge-jupyter/visionforge-jupyter-common/build.gradle.kts index c71b33d0..e6fd162b 100644 --- a/visionforge-jupyter/visionforge-jupyter-common/build.gradle.kts +++ b/visionforge-jupyter/visionforge-jupyter-common/build.gradle.kts @@ -18,15 +18,15 @@ kscience { } dependencies { - implementation(projects.visionforgeSolid) - implementation(projects.visionforgePlotly) - implementation(projects.visionforgeTables) - implementation(projects.visionforgeMarkdown) - implementation(projects.visionforgeJupyter) + api(projects.visionforgeSolid) + api(projects.visionforgePlotly) + api(projects.visionforgeTables) + api(projects.visionforgeMarkdown) + api(projects.visionforgeJupyter) } jvmMain { - implementation(projects.visionforgeGdml) + api(projects.visionforgeGdml) } jsMain { diff --git a/visionforge-server/build.gradle.kts b/visionforge-server/build.gradle.kts index 00331969..59034dd1 100644 --- a/visionforge-server/build.gradle.kts +++ b/visionforge-server/build.gradle.kts @@ -9,7 +9,7 @@ kscience{ 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") } } From 5783cf14300249f4d52599af25754178cfbbfdca Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Thu, 20 Jul 2023 08:58:41 +0300 Subject: [PATCH 13/22] Work-around for strange dependency resolution in notebooks --- demo/playground/notebooks/common-demo.ipynb | 34 ++++++++++++++++----- visionforge-jupyter/build.gradle.kts | 4 ++- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/demo/playground/notebooks/common-demo.ipynb b/demo/playground/notebooks/common-demo.ipynb index 2c397a15..165acf2d 100644 --- a/demo/playground/notebooks/common-demo.ipynb +++ b/demo/playground/notebooks/common-demo.ipynb @@ -1,5 +1,16 @@ { "cells": [ + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "//SessionOptions.resolveMpp = true" + ], + "metadata": { + "collapsed": false + } + }, { "cell_type": "code", "execution_count": null, @@ -10,9 +21,9 @@ "@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-11\")\n", - "@file:DependsOn(\"io.ktor:ktor-server-cio-jvm:2.3.0\")\n", - "@file:DependsOn(\"io.ktor:ktor-server-websockets-jvm:2.3.0\")\n", - "@file:DependsOn(\"io.ktor:ktor-server-cors-jvm:2.3.0\")" + "//@file:DependsOn(\"io.ktor:ktor-server-cio-jvm:2.3.0\")\n", + "//@file:DependsOn(\"io.ktor:ktor-server-websockets-jvm:2.3.0\")\n", + "//@file:DependsOn(\"io.ktor:ktor-server-cors-jvm:2.3.0\")" ] }, { @@ -26,10 +37,6 @@ }, "outputs": [], "source": [ - "import io.ktor.server.cio.CIO\n", - "\n", - "println(CIO)\n", - "\n", "vf.startServer()" ] }, @@ -82,7 +89,18 @@ } }, "outputs": [], - "source": [] + "source": [ + "vf.stopServer()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false + } } ], "metadata": { diff --git a/visionforge-jupyter/build.gradle.kts b/visionforge-jupyter/build.gradle.kts index ed4ab76d..49943631 100644 --- a/visionforge-jupyter/build.gradle.kts +++ b/visionforge-jupyter/build.gradle.kts @@ -13,7 +13,9 @@ kscience { api(projects.visionforgeCore) } dependencies(jvmMain){ - api("io.ktor:ktor-server-cio") + api("io.ktor:ktor-server-cio-jvm") + api("io.ktor:ktor-server-websockets-jvm") + api("io.ktor:ktor-server-cors-jvm") api(projects.visionforgeServer) } } From a49a4f1a7f5ede88dee8a171a035476cd4e9371c Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Thu, 20 Jul 2023 09:13:24 +0300 Subject: [PATCH 14/22] add plotly imports --- demo/playground/notebooks/common-demo.ipynb | 65 ++++++++++++------- .../kotlin/JupyterCommonIntegration.kt | 3 + 2 files changed, 44 insertions(+), 24 deletions(-) diff --git a/demo/playground/notebooks/common-demo.ipynb b/demo/playground/notebooks/common-demo.ipynb index 165acf2d..2e4b6110 100644 --- a/demo/playground/notebooks/common-demo.ipynb +++ b/demo/playground/notebooks/common-demo.ipynb @@ -2,59 +2,76 @@ "cells": [ { "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [ - "//SessionOptions.resolveMpp = true" - ], + "execution_count": 2, "metadata": { - "collapsed": false - } - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, + "ExecuteTime": { + "end_time": "2023-07-20T06:12:13.305060400Z", + "start_time": "2023-07-20T06:12:13.011273800Z" + } + }, "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-11\")\n", - "//@file:DependsOn(\"io.ktor:ktor-server-cio-jvm:2.3.0\")\n", - "//@file:DependsOn(\"io.ktor:ktor-server-websockets-jvm:2.3.0\")\n", - "//@file:DependsOn(\"io.ktor:ktor-server-cors-jvm:2.3.0\")" + "@file:DependsOn(\"space.kscience:visionforge-jupyter-common-jvm:0.3.0-dev-11\")" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false + }, + "ExecuteTime": { + "end_time": "2023-07-20T06:12:19.603077Z", + "start_time": "2023-07-20T06:12:19.419504300Z" } }, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": "

Starting VisionForge server on http://localhost:7777

\n" + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "vf.startServer()" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false + }, + "ExecuteTime": { + "end_time": "2023-07-20T06:12:21.490069100Z", + "start_time": "2023-07-20T06:12:20.694188600Z" } }, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": "
\n

AAA

\n
\n \n
\n
\n \n
\n
\n\n" + }, + "execution_count": 4, + "metadata": { + "text/html": { + "isolated": true + } + }, + "output_type": "execute_result" + } + ], "source": [ - "import space.kscience.visionforge.plotly.plotly\n", - "import space.kscience.plotly.*\n", - "import space.kscience.plotly.models.*\n", - "\n", "vf.page {\n", " h1 { +\"AAA\" }\n", " vision {\n", diff --git a/visionforge-jupyter/visionforge-jupyter-common/src/jvmMain/kotlin/JupyterCommonIntegration.kt b/visionforge-jupyter/visionforge-jupyter-common/src/jvmMain/kotlin/JupyterCommonIntegration.kt index 6bca7036..54f2e266 100644 --- a/visionforge-jupyter/visionforge-jupyter-common/src/jvmMain/kotlin/JupyterCommonIntegration.kt +++ b/visionforge-jupyter/visionforge-jupyter-common/src/jvmMain/kotlin/JupyterCommonIntegration.kt @@ -33,6 +33,9 @@ public class JupyterCommonIntegration : VisionForgeIntegration(CONTEXT.visionMan "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 -> From b3f68d879fd8be470f62f9aaf2567626491484f7 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sat, 22 Jul 2023 15:39:43 +0300 Subject: [PATCH 15/22] Jupyter renderers are working for lab and Idea --- build.gradle.kts | 2 +- demo/playground/build.gradle.kts | 2 +- demo/playground/notebooks/common-demo.ipynb | 85 +++------ .../visionforge/react/ThreeCanvasComponent.kt | 3 +- .../kscience/visionforge/html/HtmlFragment.kt | 33 ++-- .../visionforge/html/HtmlVisionRenderer.kt | 11 +- .../kscience/visionforge/html/VisionPage.kt | 6 +- .../kscience/visionforge/html/HtmlTagTest.kt | 4 +- .../kscience/visionforge/VisionClient.kt | 4 +- .../visionforge/html/HtmlVisionContext.kt | 8 +- .../kscience/visionforge/html/headers.kt | 6 +- .../kscience/visionforge/html/htmlExport.kt | 2 +- .../src/jsMain/kotlin/VFNotebookClient.kt | 5 +- .../src/jvmMain/kotlin/VisionForge.kt | 162 +++++++++++------- .../jvmMain/kotlin/VisionForgeIntegration.kt | 53 ++++-- .../kotlin/JupyterCommonIntegration.kt | 24 ++- .../visionforge/three/TestServerExtensions.kt | 3 +- 17 files changed, 226 insertions(+), 187 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 73f03bbd..35502755 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-11" + version = "0.3.0-dev-12" } subprojects { diff --git a/demo/playground/build.gradle.kts b/demo/playground/build.gradle.kts index 77dd565e..36bd6b7b 100644 --- a/demo/playground/build.gradle.kts +++ b/demo/playground/build.gradle.kts @@ -51,7 +51,7 @@ kotlin { implementation(projects.visionforgeMarkdown) implementation(projects.visionforgeTables) implementation(projects.cernRootLoader) - implementation(projects.visionforgeJupyter.visionforgeJupyterCommon) + api(projects.visionforgeJupyter.visionforgeJupyterCommon) } } diff --git a/demo/playground/notebooks/common-demo.ipynb b/demo/playground/notebooks/common-demo.ipynb index 2e4b6110..78797545 100644 --- a/demo/playground/notebooks/common-demo.ipynb +++ b/demo/playground/notebooks/common-demo.ipynb @@ -2,77 +2,31 @@ "cells": [ { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": { - "ExecuteTime": { - "end_time": "2023-07-20T06:12:13.305060400Z", - "start_time": "2023-07-20T06:12:13.011273800Z" - } + "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-11\")" + "@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": 3, + "execution_count": null, "metadata": { - "collapsed": false, - "jupyter": { - "outputs_hidden": false - }, - "ExecuteTime": { - "end_time": "2023-07-20T06:12:19.603077Z", - "start_time": "2023-07-20T06:12:19.419504300Z" - } + "tags": [] }, - "outputs": [ - { - "data": { - "text/html": "

Starting VisionForge server on http://localhost:7777

\n" - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "vf.startServer()" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "collapsed": false, - "jupyter": { - "outputs_hidden": false - }, - "ExecuteTime": { - "end_time": "2023-07-20T06:12:21.490069100Z", - "start_time": "2023-07-20T06:12:20.694188600Z" - } - }, - "outputs": [ - { - "data": { - "text/html": "
\n

AAA

\n
\n \n
\n
\n \n
\n
\n\n" - }, - "execution_count": 4, - "metadata": { - "text/html": { - "isolated": true - } - }, - "output_type": "execute_result" - } - ], - "source": [ - "vf.page {\n", + "vf.fragment {\n", " h1 { +\"AAA\" }\n", " vision {\n", " solid {\n", @@ -100,24 +54,27 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": false, "jupyter": { "outputs_hidden": false - } + }, + "tags": [] }, "outputs": [], "source": [ - "vf.stopServer()" + "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": { - "collapsed": false - } + "source": [] } ], "metadata": { diff --git a/ui/react/src/main/kotlin/space/kscience/visionforge/react/ThreeCanvasComponent.kt b/ui/react/src/main/kotlin/space/kscience/visionforge/react/ThreeCanvasComponent.kt index 50c8a60a..8cfa515d 100644 --- a/ui/react/src/main/kotlin/space/kscience/visionforge/react/ThreeCanvasComponent.kt +++ b/ui/react/src/main/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/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 f92454f0..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 @@ -28,7 +28,7 @@ public data class VisionPage( /** * 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-jupyter/src/jsMain/kotlin/VFNotebookClient.kt b/visionforge-jupyter/src/jsMain/kotlin/VFNotebookClient.kt index 24304136..84c6908b 100644 --- a/visionforge-jupyter/src/jsMain/kotlin/VFNotebookClient.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 @@ -20,8 +21,8 @@ public class VFNotebookClient : AbstractPlugin() { 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() { diff --git a/visionforge-jupyter/src/jvmMain/kotlin/VisionForge.kt b/visionforge-jupyter/src/jvmMain/kotlin/VisionForge.kt index 0a538a3e..d2acefde 100644 --- a/visionforge-jupyter/src/jvmMain/kotlin/VisionForge.kt +++ b/visionforge-jupyter/src/jvmMain/kotlin/VisionForge.kt @@ -11,6 +11,7 @@ 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.KotlinKernelHost import org.jetbrains.kotlinx.jupyter.api.MimeTypedResult import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.ContextAware @@ -20,24 +21,33 @@ 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.html.* import space.kscience.visionforge.server.VisionRoute import space.kscience.visionforge.server.serveVisionData 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\");" } - } + +@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, meta: Meta = Meta.EMPTY, @@ -51,29 +61,28 @@ public class VisionForge( private var engine: ApplicationEngine? = null - public var isolateFragments: Boolean = false + public var notebookMode: VisionForgeCompatibility = VisionForgeCompatibility.IDEA 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 getProperty(name: String): TypedMeta<*>? = configuration[name] ?: context.properties[name] - public fun startServer( + internal fun startServer( + kernel: KotlinKernelHost, host: String = getProperty("visionforge.host").string ?: "localhost", port: Int = getProperty("visionforge.port").int ?: VisionRoute.DEFAULT_PORT, - ): MimeTypedResult = html { + ) { if (engine != null) { - p { - style = "color: red;" - +"Stopping current VisionForge server" + kernel.displayHtml { + p { + style = "color: red;" + +"Stopping current VisionForge server" + } } + } //val connector: EngineConnectorConfig = EngineConnectorConfig(host, port) @@ -83,66 +92,91 @@ public class VisionForge( install(WebSockets) }.start(false) - p { - style = "color: blue;" - +"Starting VisionForge server on http://$host:$port" + kernel.displayHtml { + p { + style = "color: blue;" + +"Starting VisionForge server on port $port" + } } } - public fun stopServer() { + internal fun stopServer(kernel: KotlinKernelHost) { 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) + kernel.displayHtml { + p { + style = "color: red;" + +"VisionForge server stopped" } } - renderScriptForId(id) - }.finalize() + } - public fun produceHtml(isolated: Boolean? = null, fragment: HtmlVisionFragment): MimeTypedResult = - HTML(produceHtmlString(fragment), isolated ?: isolateFragments) + internal fun TagConsumer<*>.renderScriptForId(id: String, iframeIsolation: Boolean = false) { + script { + type = "text/javascript" + if (iframeIsolation) { + //language=JavaScript + unsafe { +"parent.VisionForge.renderAllVisionsById(document, \"$id\");" } + } else { + //language=JavaScript + unsafe { +"VisionForge.renderAllVisionsById(document, \"$id\");" } + } + } + } - public fun fragment(body: HtmlVisionFragment): MimeTypedResult = produceHtml(fragment = body) - public fun page(body: HtmlVisionFragment): MimeTypedResult = produceHtml(true, body) + + public fun produceHtml( + isolated: Boolean? = null, + fragment: HtmlVisionFragment, + ): MimeTypedResult { + val iframeIsolation = isolated + ?: (notebookMode == VisionForgeCompatibility.JUPYTER || notebookMode == VisionForgeCompatibility.DATALORE) + 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, iframeIsolation = iframeIsolation) + } + } 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 index 3029ef32..e7175dd0 100644 --- a/visionforge-jupyter/src/jvmMain/kotlin/VisionForgeIntegration.kt +++ b/visionforge-jupyter/src/jvmMain/kotlin/VisionForgeIntegration.kt @@ -1,8 +1,7 @@ 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.MimeTypedResult import org.jetbrains.kotlinx.jupyter.api.declare import org.jetbrains.kotlinx.jupyter.api.libraries.JupyterIntegration import space.kscience.dataforge.context.Context @@ -31,11 +30,12 @@ public abstract class VisionForgeIntegration( onLoaded { declare("VisionForge" to handler, "vf" to handler) + handler.startServer(this) } onShutdown { - handler.stopServer() + handler.stopServer(this) } import( @@ -43,14 +43,14 @@ public abstract class VisionForgeIntegration( "space.kscience.visionforge.html.*", "space.kscience.visionforge.jupyter.*" ) - - render { fragment -> - handler.produceHtml(fragment = fragment) - } - - render { fragment -> - handler.produceHtml(fragment = fragment) - } +// +// render { fragment -> +// HTML(fragment.renderToString()) +// } +// +// render { fragment -> +// handler.produceHtml(fragment = fragment) +// } render { vision -> handler.produceHtml { @@ -59,13 +59,13 @@ public abstract class VisionForgeIntegration( } render { page -> - HTML(createHTML().apply { + HTML(true) { head { meta { charset = "utf-8" } page.pageHeaders.values.forEach { - fragment(it) + appendFragment(it) } } body { @@ -74,9 +74,11 @@ public abstract class VisionForgeIntegration( this.id = id visionFragment(visionManager, fragment = page.content) } - renderScriptForId(id) + with(handler) { + renderScriptForId(id, true) + } } - }.finalize(), true) + } } render { fragment -> @@ -87,7 +89,7 @@ public abstract class VisionForgeIntegration( +"The server is not running. Forms are not interactive. Start server with `VisionForge.startServer()." } } - fragment(fragment.formBody) + appendFragment(fragment.formBody) vision(fragment.vision) } } @@ -96,10 +98,25 @@ public abstract class VisionForgeIntegration( } } + +/** + * 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(), - content: HtmlVisionFragment -): VisionPage = VisionPage(visionManager, pageHeaders, content) \ No newline at end of file + body: VisionTagConsumer<*>.() -> Unit, +): VisionPage = VisionPage(visionManager, pageHeaders, body) + diff --git a/visionforge-jupyter/visionforge-jupyter-common/src/jvmMain/kotlin/JupyterCommonIntegration.kt b/visionforge-jupyter/visionforge-jupyter-common/src/jvmMain/kotlin/JupyterCommonIntegration.kt index 54f2e266..c326a816 100644 --- a/visionforge-jupyter/visionforge-jupyter-common/src/jvmMain/kotlin/JupyterCommonIntegration.kt +++ b/visionforge-jupyter/visionforge-jupyter-common/src/jvmMain/kotlin/JupyterCommonIntegration.kt @@ -6,8 +6,12 @@ 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 @@ -23,7 +27,7 @@ public class JupyterCommonIntegration : VisionForgeIntegration(CONTEXT.visionMan override fun Builder.afterLoaded() { resources { - js("three") { + js("visionforge-common") { classPath("js/visionforge-jupyter-common.js") } } @@ -55,6 +59,24 @@ public class JupyterCommonIntegration : VisionForgeIntegration(CONTEXT.visionMan 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 { 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 ef4eb39b..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,6 +3,7 @@ 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 @@ -15,7 +16,7 @@ class TestServerExtensions { VisionPage.importScriptHeader( "js/visionforge-three.js", ResourceLocation.SYSTEM - ).invoke(this) + ).appendTo(this) }.finalize() From ed491bdae093d5e54ba5f6289d3350a3cd94f7b6 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sat, 22 Jul 2023 18:25:11 +0300 Subject: [PATCH 16/22] cleanup --- .../src/jsMain/kotlin/VFNotebookClient.kt | 5 +-- .../src/jvmMain/kotlin/VisionForge.kt | 35 ++++++++----------- .../jvmMain/kotlin/VisionForgeIntegration.kt | 33 +++++++++++------ .../kotlin/JupyterCommonIntegration.kt | 8 ++--- 4 files changed, 44 insertions(+), 37 deletions(-) diff --git a/visionforge-jupyter/src/jsMain/kotlin/VFNotebookClient.kt b/visionforge-jupyter/src/jsMain/kotlin/VFNotebookClient.kt index 84c6908b..7f06c6ac 100644 --- a/visionforge-jupyter/src/jsMain/kotlin/VFNotebookClient.kt +++ b/visionforge-jupyter/src/jsMain/kotlin/VFNotebookClient.kt @@ -31,9 +31,10 @@ public class VFNotebookClient : 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") diff --git a/visionforge-jupyter/src/jvmMain/kotlin/VisionForge.kt b/visionforge-jupyter/src/jvmMain/kotlin/VisionForge.kt index d2acefde..49d5fe23 100644 --- a/visionforge-jupyter/src/jvmMain/kotlin/VisionForge.kt +++ b/visionforge-jupyter/src/jvmMain/kotlin/VisionForge.kt @@ -10,9 +10,7 @@ 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.KotlinKernelHost -import org.jetbrains.kotlinx.jupyter.api.MimeTypedResult +import org.jetbrains.kotlinx.jupyter.api.* import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.ContextAware import space.kscience.dataforge.context.info @@ -21,7 +19,8 @@ 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.* +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 @@ -50,6 +49,7 @@ public enum class VisionForgeCompatibility { @Suppress("ExtractKtorModule") public class VisionForge( public val visionManager: VisionManager, + public val notebook: Notebook, meta: Meta = Meta.EMPTY, ) : ContextAware, CoroutineScope { @@ -61,8 +61,6 @@ public class VisionForge( private var engine: ApplicationEngine? = null - public var notebookMode: VisionForgeCompatibility = VisionForgeCompatibility.IDEA - override val coroutineContext: CoroutineContext get() = context.coroutineContext @@ -75,19 +73,19 @@ public class VisionForge( host: String = getProperty("visionforge.host").string ?: "localhost", port: Int = getProperty("visionforge.port").int ?: VisionRoute.DEFAULT_PORT, ) { - if (engine != null) { + engine?.let { kernel.displayHtml { p { style = "color: red;" +"Stopping current VisionForge server" } } - + it.stop(1000, 2000) } //val connector: EngineConnectorConfig = EngineConnectorConfig(host, port) - engine?.stop(1000, 2000) + engine = context.embeddedServer(CIO, port, host) { install(WebSockets) }.start(false) @@ -115,16 +113,11 @@ public class VisionForge( } } - internal fun TagConsumer<*>.renderScriptForId(id: String, iframeIsolation: Boolean = false) { + internal fun TagConsumer<*>.renderScriptForId(id: String) { script { type = "text/javascript" - if (iframeIsolation) { - //language=JavaScript - unsafe { +"parent.VisionForge.renderAllVisionsById(document, \"$id\");" } - } else { - //language=JavaScript - unsafe { +"VisionForge.renderAllVisionsById(document, \"$id\");" } - } + //language=JavaScript + unsafe { +"parent.VisionForge.renderAllVisionsById(document, \"$id\");" } } } @@ -133,8 +126,10 @@ public class VisionForge( isolated: Boolean? = null, fragment: HtmlVisionFragment, ): MimeTypedResult { - val iframeIsolation = isolated - ?: (notebookMode == VisionForgeCompatibility.JUPYTER || notebookMode == VisionForgeCompatibility.DATALORE) + val iframeIsolation = isolated ?: when (notebook.jupyterClientType) { + JupyterClientType.DATALORE, JupyterClientType.JUPYTER_NOTEBOOK -> true + else -> false + } return HTML( iframeIsolation ) { @@ -172,7 +167,7 @@ public class VisionForge( visionFragment(visionManager, fragment = fragment) } } - renderScriptForId(id, iframeIsolation = iframeIsolation) + renderScriptForId(id) } } diff --git a/visionforge-jupyter/src/jvmMain/kotlin/VisionForgeIntegration.kt b/visionforge-jupyter/src/jvmMain/kotlin/VisionForgeIntegration.kt index e7175dd0..afc2ecc2 100644 --- a/visionforge-jupyter/src/jvmMain/kotlin/VisionForgeIntegration.kt +++ b/visionforge-jupyter/src/jvmMain/kotlin/VisionForgeIntegration.kt @@ -1,6 +1,7 @@ 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 @@ -22,20 +23,30 @@ public abstract class VisionForgeIntegration( ) : JupyterIntegration(), ContextAware { override val context: Context get() = visionManager.context - protected val handler: VisionForge = VisionForge(visionManager) - protected abstract fun Builder.afterLoaded() + protected abstract fun Builder.afterLoaded(vf: VisionForge) final override fun Builder.onLoaded() { + val vf: VisionForge = VisionForge(visionManager, notebook) + onLoaded { - declare("VisionForge" to handler, "vf" to handler) - handler.startServer(this) + 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 { - handler.stopServer(this) + vf.stopServer(this) } import( @@ -53,7 +64,7 @@ public abstract class VisionForgeIntegration( // } render { vision -> - handler.produceHtml { + vf.produceHtml { vision(vision) } } @@ -74,16 +85,16 @@ public abstract class VisionForgeIntegration( this.id = id visionFragment(visionManager, fragment = page.content) } - with(handler) { - renderScriptForId(id, true) + with(vf) { + renderScriptForId(id) } } } } render { fragment -> - handler.produceHtml { - if (!handler.isServerRunning()) { + vf.produceHtml { + if (!vf.isServerRunning()) { p { style = "color: red;" +"The server is not running. Forms are not interactive. Start server with `VisionForge.startServer()." @@ -94,7 +105,7 @@ public abstract class VisionForgeIntegration( } } - afterLoaded() + afterLoaded(vf) } } diff --git a/visionforge-jupyter/visionforge-jupyter-common/src/jvmMain/kotlin/JupyterCommonIntegration.kt b/visionforge-jupyter/visionforge-jupyter-common/src/jvmMain/kotlin/JupyterCommonIntegration.kt index c326a816..6200bd5d 100644 --- a/visionforge-jupyter/visionforge-jupyter-common/src/jvmMain/kotlin/JupyterCommonIntegration.kt +++ b/visionforge-jupyter/visionforge-jupyter-common/src/jvmMain/kotlin/JupyterCommonIntegration.kt @@ -24,7 +24,7 @@ import space.kscience.visionforge.visionManager @DFExperimental public class JupyterCommonIntegration : VisionForgeIntegration(CONTEXT.visionManager) { - override fun Builder.afterLoaded() { + override fun Builder.afterLoaded(vf: VisionForge) { resources { js("visionforge-common") { @@ -43,19 +43,19 @@ public class JupyterCommonIntegration : VisionForgeIntegration(CONTEXT.visionMan ) render { gdmlModel -> - handler.produceHtml { + vf.produceHtml { vision { gdmlModel.toVision() } } } render> { table -> - handler.produceHtml { + vf.produceHtml { vision { table.toVision() } } } render { plot -> - handler.produceHtml { + vf.produceHtml { vision { plot.asVision() } } } From f5fba4747eb6e107b761f0c8cd6513cbb7236dc6 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Tue, 25 Jul 2023 13:35:55 +0300 Subject: [PATCH 17/22] Some refactoring and new server demo --- CHANGELOG.md | 2 + build.gradle.kts | 2 +- .../kotlin/ru/mipt/npm/root/dRootToSolid.kt | 2 +- .../visionforge/gdml/demo/GDMLAppComponent.kt | 4 +- .../visionforge/gdml/demo/GdmlJsDemoApp.kt | 4 +- .../src/main/kotlin/JsPlaygroundApp.kt | 4 +- .../src/main/kotlin/gravityDemo.kt | 4 +- .../kotlin/ru/mipt/npm/muon/monitor/Model.kt | 6 +- .../mipt/npm/muon/monitor/MMAppComponent.kt | 4 +- demo/playground/src/jvmMain/kotlin/antenna.kt | 91 +++++++++++++++---- .../src/jvmMain/kotlin/gdmlCurve.kt | 4 +- .../src/jvmMain/kotlin/randomSpheres.kt | 4 +- .../src/jvmMain/kotlin/rootParser.kt | 4 +- .../src/jvmMain/kotlin/serverExtensions.kt | 52 +++++++++++ demo/playground/src/jvmMain/kotlin/shapes.kt | 8 +- .../src/jvmMain/kotlin/simpleCube.kt | 4 +- .../main/kotlin/ru/mipt/npm/sat/geometry.kt | 2 +- .../main/kotlin/ru/mipt/npm/sat/satServer.kt | 4 +- .../src/main/kotlin/ru/mipt/npm/sat/static.kt | 4 +- .../kscience/visionforge/solid/demo/demo.kt | 30 +++--- .../ThreeWithControlsPlugin.kt | 18 +++- .../visionforge/gdml/GdmlLoaderOptions.kt | 2 +- .../visionforge/server/VisionServer.kt | 6 +- .../visionforge/solid/ColorAccessor.kt | 8 +- .../kscience/visionforge/solid/LightSource.kt | 2 +- .../kscience/visionforge/solid/SolidGroup.kt | 5 + .../kscience/visionforge/solid/Solids.kt | 4 + .../visionforge/solid/CompositeTest.kt | 2 +- .../kscience/visionforge/solid/GroupTest.kt | 6 +- .../visionforge/solid/SerializationTest.kt | 6 +- .../visionforge/solid/SolidPropertyTest.kt | 4 +- .../visionforge/solid/SolidReferenceTest.kt | 2 +- .../visionforge/solid/VisionUpdateTest.kt | 4 +- 33 files changed, 215 insertions(+), 93 deletions(-) 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 35502755..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-12" + 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 a2e1f70a..be9372b5 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 @@ -328,7 +328,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) { 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/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/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 82c20def..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 @@ -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/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/playground/src/jvmMain/kotlin/antenna.kt b/demo/playground/src/jvmMain/kotlin/antenna.kt index cd711d11..6e9b759b 100644 --- a/demo/playground/src/jvmMain/kotlin/antenna.kt +++ b/demo/playground/src/jvmMain/kotlin/antenna.kt @@ -1,16 +1,19 @@ 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.geometry.Angle -import space.kscience.kmath.geometry.Euclidean3DSpace -import space.kscience.kmath.geometry.degrees -import space.kscience.kmath.geometry.fromRotation -import space.kscience.visionforge.html.ResourceLocation +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() = makeVisionFile(resourceLocation = ResourceLocation.SYSTEM) { +fun main() = serve { val azimuth = 60.degrees val inclination = 15.degrees @@ -22,28 +25,76 @@ fun main() = makeVisionFile(resourceLocation = ResourceLocation.SYSTEM) { //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 { + + solid(options = { + configure { "controls.enabled" put false } + }) { rotationX = -PI / 2 rotationZ = PI - axes(200) + //axes(200) ambientLight() - cylinder(50, 5, name = "base") - solidGroup("frame") { - z = 60 - axes(200) - solidGroup("antenna") { - tube(40, 10, 30) - sphereLayer(100, 95, theta = PI / 6) { - z = 100 - rotationX = -PI / 2 + val platform = solidGroup("platform") { + cylinder(50, 5, name = "base") + solidGroup("frame") { + z = 60 + + val antenna = 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 } - cylinder(5, 30) { - z = 15 + } + } + + 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 } - quaternion = direction } } } 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..d5fea32e 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 @@ -44,7 +44,7 @@ fun main() { requirePlugin(Solids) solid { ambientLight { - color.set(Colors.white) + color(Colors.white) } rootGeo(geo,"BM@N", maxLayer = 3, 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/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/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 baee4c71..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 @@ -23,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) @@ -49,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) } } @@ -80,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) { @@ -95,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)) } } } @@ -114,7 +114,7 @@ fun VisionLayout.showcase() { rotate((PI/20).radians,Euclidean3DSpace.yAxis) } } - color.set(Colors.red) + color(Colors.red) } } } @@ -127,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 } } @@ -136,7 +136,7 @@ fun VisionLayout.showcase() { sphere(100) { detail = 32 opacity = 0.4 - color.set(Colors.blue) + color(Colors.blue) } repeat(20) { polyline( @@ -145,7 +145,7 @@ fun VisionLayout.showcase() { ) { thickness = 3.0 rotationX = it * PI2 / 20 - color.set(Colors.green) + color(Colors.green) //rotationY = it * PI2 / 20 } } @@ -176,7 +176,7 @@ fun VisionLayout.showcaseCSG() { detail = 32 } material { - color.set(Colors.pink) + color(Colors.pink) } } composite(CompositeType.UNION) { @@ -186,7 +186,7 @@ fun VisionLayout.showcaseCSG() { sphere(50) { detail = 32 } - color.set("lightgreen") + color("lightgreen") opacity = 0.7 } composite(CompositeType.SUBTRACT) { @@ -197,7 +197,7 @@ fun VisionLayout.showcaseCSG() { sphere(50) { detail = 32 } - color.set("teal") + color("teal") opacity = 0.7 } } @@ -208,7 +208,7 @@ fun VisionLayout.showcaseCSG() { detail = 32 } box(100, 100, 100) - color.set("red") + color("red") opacity = 0.5 } } diff --git a/ui/ring/src/main/kotlin/space.kscience.visionforge.ring/ThreeWithControlsPlugin.kt b/ui/ring/src/main/kotlin/space.kscience.visionforge.ring/ThreeWithControlsPlugin.kt index dd7f8e0d..94259b2f 100644 --- a/ui/ring/src/main/kotlin/space.kscience.visionforge.ring/ThreeWithControlsPlugin.kt +++ b/ui/ring/src/main/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-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-server/src/main/kotlin/space/kscience/visionforge/server/VisionServer.kt b/visionforge-server/src/main/kotlin/space/kscience/visionforge/server/VisionServer.kt index 47c5db84..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 @@ -57,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) @@ -170,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/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/LightSource.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/LightSource.kt index beeb4eb3..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 { 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 791bb0c9..8e04b638 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 @@ -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, 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 e9301f67..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 @@ -94,3 +94,7 @@ public inline fun VisionOutput.solid(options: Canvas3DOptions? = null, block: So } } } + +@VisionBuilder +public inline fun VisionOutput.solid(options: Canvas3DOptions.() -> Unit, block: SolidGroup.() -> Unit): SolidGroup = + solid(Canvas3DOptions(options), block) 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/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())) From 1e29c5dbaa7a52ea5cde8513ac82a4a8a134455c Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Tue, 22 Aug 2023 21:47:07 +0300 Subject: [PATCH 18/22] add a workaround for root duplicating names --- .../kotlin/ru/mipt/npm/root/dRootToSolid.kt | 27 +++++++++--------- .../src/jvmMain/kotlin/rootParser.kt | 2 +- .../resources/root/geometry_run_7-2076.zip | Bin 0 -> 48731 bytes .../kscience/visionforge/solid/SolidGroup.kt | 2 +- .../visionforge/solid/SolidReference.kt | 15 +++++++--- 5 files changed, 27 insertions(+), 19 deletions(-) create mode 100644 demo/playground/src/jvmMain/resources/root/geometry_run_7-2076.zip 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 be9372b5..13b1cb55 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,6 +4,7 @@ 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.visionforge.MutableVisionContainer import space.kscience.visionforge.isEmpty import space.kscience.visionforge.set @@ -223,7 +224,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) } } @@ -348,29 +349,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/demo/playground/src/jvmMain/kotlin/rootParser.kt b/demo/playground/src/jvmMain/kotlin/rootParser.kt index d5fea32e..85a2f0d3 100644 --- a/demo/playground/src/jvmMain/kotlin/rootParser.kt +++ b/demo/playground/src/jvmMain/kotlin/rootParser.kt @@ -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() } 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 0000000000000000000000000000000000000000..1680404cd90cbb11d16f618bbda808c0c0208873 GIT binary patch literal 48731 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.() -> Unit) 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..7203f06f 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,9 +228,14 @@ 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: Name, + name: String? = null, +): SolidReference = ref(templateName, name?.parseAsName()) + public fun MutableVisionContainer.ref( templateName: String, name: String? = null, From 3529d0efc64302e538694638c75a58c8d8a4ff45 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Tue, 22 Aug 2023 22:52:17 +0300 Subject: [PATCH 19/22] Fix polygon builders --- .../kotlin/ru/mipt/npm/root/dRootToSolid.kt | 6 +++--- .../playground/src/jvmMain/kotlin/extruded.kt | 21 +++++++++++++++++++ .../src/jvmMain/kotlin/rootParser.kt | 2 +- .../kscience/visionforge/VisionManager.kt | 2 -- .../kscience/visionforge/gdml/gdmlLoader.kt | 4 ++-- .../kscience/visionforge/solid/Extruded.kt | 2 -- 6 files changed, 27 insertions(+), 10 deletions(-) create mode 100644 demo/playground/src/jvmMain/kotlin/extruded.kt 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 13b1cb55..07112c06 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 @@ -209,9 +209,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.. 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/rootParser.kt b/demo/playground/src/jvmMain/kotlin/rootParser.kt index 85a2f0d3..1faa8d0b 100644 --- a/demo/playground/src/jvmMain/kotlin/rootParser.kt +++ b/demo/playground/src/jvmMain/kotlin/rootParser.kt @@ -46,7 +46,7 @@ fun main() { ambientLight { 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/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-gdml/src/commonMain/kotlin/space/kscience/visionforge/gdml/gdmlLoader.kt b/visionforge-gdml/src/commonMain/kotlin/space/kscience/visionforge/gdml/gdmlLoader.kt index 230af4b0..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 @@ -207,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.. 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 1a6487b9..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 @@ -23,8 +23,6 @@ public class Shape2DBuilder(private val points: ArrayList = Arr points.add(Float32Vector2D(x, y)) } - public infix fun Number.to(y: Number): Unit = point(this, y) - public fun build(): Shape2D = points } From cd28f377abb4c2b0c033a6861a47d7cef4b24023 Mon Sep 17 00:00:00 2001 From: SPC-code <112205870+SPC-code@users.noreply.github.com> Date: Wed, 23 Aug 2023 09:24:17 +0300 Subject: [PATCH 20/22] Update build.yml --- .github/workflows/build.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 From 6a801f9999df5d29640802f74f6bff5c93ceff51 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 23 Aug 2023 09:34:58 +0300 Subject: [PATCH 21/22] Fix solid ref signature --- .../kscience/visionforge/solid/SolidReference.kt | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) 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 7203f06f..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 @@ -233,13 +233,8 @@ public fun MutableVisionContainer.ref( public fun MutableVisionContainer.ref( templateName: Name, - name: String? = null, -): SolidReference = ref(templateName, name?.parseAsName()) - -public fun MutableVisionContainer.ref( - templateName: String, - name: String? = null, -): SolidReference = ref(Name.parse(templateName), name) + name: String, +): SolidReference = ref(templateName, name.parseAsName()) /** * Add new [SolidReference] wrapping given object and automatically adding it to the prototypes. @@ -258,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()) } From 92bfb173d8b49cb8b951a1b3f665ec310188069c Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sat, 16 Sep 2023 12:27:09 +0300 Subject: [PATCH 22/22] minor fix to Root rotations --- .../kotlin/ru/mipt/npm/root/dRootToSolid.kt | 15 +++++++++------ demo/playground/src/jvmMain/kotlin/antenna.kt | 14 +++++++------- .../space/kscience/visionforge/solid/Solid.kt | 6 +++--- .../visionforge/solid/three/ThreeFactory.kt | 2 +- 4 files changed, 20 insertions(+), 17 deletions(-) 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 07112c06..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 @@ -5,12 +5,17 @@ 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() @@ -28,12 +33,10 @@ 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 = Float32Vector3D(xAngle, yAngle, zAngle) + val matrix = VirtualMatrix(3, 3) { i, j -> rot[i * 3 + j] } + quaternion = Quaternion.fromRotationMatrix(matrix) } private fun Solid.translate(trans: DoubleArray) { diff --git a/demo/playground/src/jvmMain/kotlin/antenna.kt b/demo/playground/src/jvmMain/kotlin/antenna.kt index 6e9b759b..5ec997f7 100644 --- a/demo/playground/src/jvmMain/kotlin/antenna.kt +++ b/demo/playground/src/jvmMain/kotlin/antenna.kt @@ -15,13 +15,13 @@ import kotlin.math.sin fun main() = serve { - val azimuth = 60.degrees - val inclination = 15.degrees +// 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 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) @@ -43,7 +43,7 @@ fun main() = serve { solidGroup("frame") { z = 60 - val antenna = solidGroup("antenna") { + solidGroup("antenna") { axes(200) tube(40, 10, 30) sphereLayer(100, 95, theta = PI / 6) { 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 8b719b54..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 @@ -206,7 +206,7 @@ public var Solid.rotationZ: Number by float(Z_ROTATION_KEY, 0f) /** * Raw quaternion value defined in properties */ -public var Solid.quaternionValue: Quaternion? +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" } Quaternion(it[0].float, it[1].float, it[2].float, it[3].float) @@ -229,14 +229,14 @@ public var Solid.quaternionValue: Quaternion? * Quaternion value including information from euler angles */ public var Solid.quaternion: Quaternion - get() = quaternionValue ?: Quaternion.fromEuler( + get() = quaternionOrNull ?: Quaternion.fromEuler( rotationX.radians, rotationY.radians, rotationZ.radians, rotationOrder ) set(value) { - quaternionValue = value + quaternionOrNull = value } public var Solid.scaleX: Number by float(X_SCALE_KEY, 1f) diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeFactory.kt index 46e3d41b..122a2c30 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeFactory.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeFactory.kt @@ -41,7 +41,7 @@ public fun Object3D.updatePosition(vision: Vision) { if (vision is Solid) { position.set(vision.x, vision.y, vision.z) - val quaternion = vision.quaternionValue + val quaternion = vision.quaternionOrNull if (quaternion != null) { setRotationFromQuaternion(