Merge branch 'dev' into beta/1.9.0

# Conflicts:
#	jupyter/visionforge-jupyter-gdml/build.gradle.kts
#	visionforge-jupyter/build.gradle.kts
#	visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeAxesFactory.kt
#	visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeGeometryBuilder.kt
#	visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeStlFactory.kt
This commit is contained in:
Alexander Nozik 2023-09-16 12:30:52 +03:00
commit 20fc81305c
131 changed files with 1615 additions and 960 deletions

View File

@ -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

View File

@ -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

View File

@ -13,7 +13,7 @@ val fxVersion by extra("11")
allprojects {
group = "space.kscience"
version = "0.3.0-dev-9"
version = "0.3.0-dev-13"
}
subprojects {

View File

@ -4,12 +4,18 @@ import space.kscience.dataforge.meta.*
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.parseAsName
import space.kscience.dataforge.names.plus
import space.kscience.dataforge.names.withIndex
import space.kscience.kmath.complex.Quaternion
import space.kscience.kmath.geometry.fromRotationMatrix
import space.kscience.kmath.linear.VirtualMatrix
import space.kscience.visionforge.MutableVisionContainer
import space.kscience.visionforge.isEmpty
import space.kscience.visionforge.set
import space.kscience.visionforge.solid.*
import space.kscience.visionforge.solid.SolidMaterial.Companion.MATERIAL_COLOR_KEY
import kotlin.math.*
import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.sin
private val volumesName = Name.EMPTY //"volumes".asName()
@ -27,17 +33,15 @@ private data class RootToSolidContext(
val colorCache: MutableMap<Meta, String> = mutableMapOf(),
)
// converting to XYZ to TaitBryan angles according to https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix
// apply rotation from a matrix
private fun Solid.rotate(rot: DoubleArray) {
val xAngle = atan2(-rot[5], rot[8])
val yAngle = atan2(rot[2], sqrt(1.0 - rot[2].pow(2)))
val zAngle = atan2(-rot[1], rot[0])
rotation = Point3D(xAngle, yAngle, zAngle)
val matrix = VirtualMatrix(3, 3) { i, j -> rot[i * 3 + j] }
quaternion = Quaternion.fromRotationMatrix(matrix)
}
private fun Solid.translate(trans: DoubleArray) {
val (x, y, z) = trans
position = Point3D(x, y, z)
position = Float32Vector3D(x, y, z)
}
private fun Solid.useMatrix(matrix: DGeoMatrix?) {
@ -72,7 +76,7 @@ private fun Solid.useMatrix(matrix: DGeoMatrix?) {
val fScale by matrix.meta.doubleArray()
translate(fTranslation)
rotate(fRotationMatrix)
scale = Point3D(fScale[0], fScale[1], fScale[2])
scale = Float32Vector3D(fScale[0], fScale[1], fScale[2])
}
}
}
@ -208,9 +212,9 @@ private fun SolidGroup.addShape(
require(fNz > 1) { "The polyhedron geometry requires at least two planes" }
val baseRadius = fRmax[0]
shape {
(0..fNedges).forEach {
val phi = deltaphi * fNedges * it + startphi
(baseRadius * cos(phi) to baseRadius * sin(phi))
(0..<fNedges).forEach {
val phi = deltaphi / fNedges * it + startphi
point(baseRadius * cos(phi), baseRadius * sin(phi))
}
}
(0 until fNz).forEach { index ->
@ -223,7 +227,7 @@ private fun SolidGroup.addShape(
"TGeoShapeAssembly" -> {
val fVolume by shape.dObject(::DGeoVolume)
fVolume?.let { volume ->
addRootVolume(volume, context, block = block)
addRootVolume(volume, context, name = volume.fName.ifEmpty { null }, block = block)
}
}
@ -248,14 +252,14 @@ private fun SolidGroup.addShape(
val fDz by shape.meta.double(0.0)
//TODO check proper node order
val node1 = Point3D(-fBl1, -fH1, -fDz)
val node2 = Point3D(fBl1, -fH1, -fDz)
val node3 = Point3D(fTl1, fH1, -fDz)
val node4 = Point3D(-fTl1, fH1, -fDz)
val node5 = Point3D(-fBl2, -fH2, fDz)
val node6 = Point3D(fBl2, -fH2, fDz)
val node7 = Point3D(fTl2, fH2, fDz)
val node8 = Point3D(-fTl2, fH2, fDz)
val node1 = Float32Vector3D(-fBl1, -fH1, -fDz)
val node2 = Float32Vector3D(fBl1, -fH1, -fDz)
val node3 = Float32Vector3D(fTl1, fH1, -fDz)
val node4 = Float32Vector3D(-fTl1, fH1, -fDz)
val node5 = Float32Vector3D(-fBl2, -fH2, fDz)
val node6 = Float32Vector3D(fBl2, -fH2, fDz)
val node7 = Float32Vector3D(fTl2, fH2, fDz)
val node8 = Float32Vector3D(-fTl2, fH2, fDz)
hexagon(node1, node2, node3, node4, node5, node6, node7, node8, name)
}
@ -264,7 +268,7 @@ private fun SolidGroup.addShape(
val fScale by shape.dObject(::DGeoScale)
fShape?.let { scaledShape ->
solidGroup(name?.let { Name.parse(it) }) {
scale = Point3D(fScale?.x ?: 1.0, fScale?.y ?: 1.0, fScale?.z ?: 1.0)
scale = Float32Vector3D(fScale?.x ?: 1.0, fScale?.y ?: 1.0, fScale?.z ?: 1.0)
addShape(scaledShape, context)
apply(block)
}
@ -328,7 +332,7 @@ private fun buildVolume(volume: DGeoVolume, context: RootToSolidContext): Solid?
group
}.apply {
volume.fMedium?.let { medium ->
color.set(context.colorCache.getOrPut(medium.meta) { RootColors[11 + context.colorCache.size] })
color(context.colorCache.getOrPut(medium.meta) { RootColors[11 + context.colorCache.size] })
}
if (!context.ignoreRootColors) {
@ -348,29 +352,29 @@ private fun SolidGroup.addRootVolume(
cache: Boolean = true,
block: Solid.() -> Unit = {},
) {
val combinedName = if (volume.fName.isEmpty()) {
name
} else if (name == null) {
volume.fName
} else {
"${name}_${volume.fName}"
val combinedName = name?.parseAsName()?.let {
// this fix is required to work around malformed root files with duplicated node names
if (get(it) != null) {
it.withIndex(volume.hashCode().toString(16))
} else {
it
}
}
if (!cache) {
val group = buildVolume(volume, context)?.apply(block)
setChild(combinedName?.let { Name.parse(it) }, group)
val group = buildVolume(volume, context)?.apply(block) ?: return
setChild(combinedName, group)
} else {
val templateName = volumesName + volume.name
val existing = getPrototype(templateName)
val existing = context.prototypeHolder.getPrototype(templateName)
if (existing == null) {
context.prototypeHolder.prototypes {
val group = buildVolume(volume, context)
val group = buildVolume(volume, context) ?: return@prototypes
setChild(templateName, group)
}
}
ref(templateName, name).apply(block)
ref(templateName, combinedName).apply(block)
}
}

View File

@ -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)
}
}
}

View File

@ -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<GDMLAppProps>("GDMLApp") { props ->
console.info("Marking layers for file $name")
markLayers()
ambientLight {
color.set(Colors.white)
color(Colors.white)
}
}
}

View File

@ -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))

View File

@ -7,7 +7,8 @@ kscience{
}
kotlin{
js(IR){
explicitApi = null
js{
useCommonJs()
browser {
binaries.executable()

View File

@ -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
}

View File

@ -42,13 +42,13 @@ val GravityDemo = fc<DemoProps> { 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 {

View File

@ -40,6 +40,8 @@ kscience {
}
}
kotlin.explicitApi = null
application {
mainClass.set("ru.mipt.npm.muon.monitor.server.MMServerKt")
}

View File

@ -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<Point3D>
typealias Track = List<Float32Vector3D>
/**
*

View File

@ -16,7 +16,7 @@ class Model(val manager: VisionManager) {
private fun MutableVisionContainer<Solid>.pixel(pixel: SC1) {
val group = solidGroup(pixel.name) {
position = Point3D(pixel.center.x, pixel.center.y, pixel.center.z)
position = Float32Vector3D(pixel.center.x, pixel.center.y, pixel.center.z)
box(pixel.xSize, pixel.ySize, pixel.zSize)
label(pixel.name) {
z = -Monitor.PIXEL_Z_SIZE / 2 - 5
@ -39,7 +39,7 @@ class Model(val manager: VisionManager) {
val root: SolidGroup = SolidGroup().apply {
setAsRoot(this@Model.manager)
material {
color.set("darkgreen")
color("darkgreen")
}
rotationX = PI / 2
solidGroup("bottom") {
@ -64,7 +64,7 @@ class Model(val manager: VisionManager) {
private fun highlight(pixel: String) {
println("highlight $pixel")
map[pixel]?.color.set("blue")
map[pixel]?.color("blue")
}
fun reset() {
@ -82,7 +82,7 @@ class Model(val manager: VisionManager) {
}
event.track?.let {
tracks.polyline(*it.toTypedArray(), name = "track[${event.id}]") {
color.set("red")
color("red")
}
}
}

View File

@ -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
}

View File

@ -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<MMAppProps>("Muon monitor") { props ->
props.model.root.apply {
edges()
ambientLight{
color.set(Colors.white)
color(Colors.white)
}
}
}

View File

@ -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"))

View File

@ -5,7 +5,7 @@ import org.apache.commons.math3.geometry.euclidean.threed.Plane
import org.apache.commons.math3.geometry.euclidean.threed.Vector3D
import ru.mipt.npm.muon.monitor.Monitor.CENTRAL_LAYER_Z
import ru.mipt.npm.muon.monitor.Monitor.GEOMETRY_TOLERANCE
import space.kscience.visionforge.solid.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<Point3D> {
fun Line.toKMathVectors(): List<Float32Vector3D> {
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())
}

View File

@ -43,7 +43,7 @@ fun readEffs(): Map<String, Double> {
fun buildEventByTrack(index: Int, track: Line, hitResolver: (Line) -> Collection<SC1> = 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<SC1> = { track: Line ->

View File

@ -47,12 +47,11 @@ kotlin {
val commonMain by getting {
dependencies {
implementation(projects.visionforgeSolid)
implementation(projects.visionforgeGdml)
implementation(projects.visionforgePlotly)
implementation(projects.visionforgeMarkdown)
implementation(projects.visionforgeTables)
implementation(projects.cernRootLoader)
implementation(projects.jupyter)
api(projects.visionforgeJupyter.visionforgeJupyterCommon)
}
}
@ -66,6 +65,8 @@ kotlin {
val jvmMain by getting {
dependencies {
implementation("io.ktor:ktor-server-cio:${spclibs.versions.ktor.get()}")
implementation(projects.visionforgeGdml)
implementation(projects.visionforgeServer)
implementation(spclibs.logback.classic)
implementation("com.github.Ricky12Awesome:json-schema-serialization:0.6.6")

View File

@ -0,0 +1,101 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"@file:Repository(\"*mavenLocal\")\n",
"@file:Repository(\"https://repo.kotlin.link\")\n",
"@file:Repository(\"https://maven.pkg.jetbrains.space/spc/p/sci/dev\")\n",
"@file:DependsOn(\"space.kscience:visionforge-jupyter-common-jvm:0.3.0-dev-12\")\n",
"//import space.kscience.visionforge.jupyter.JupyterCommonIntegration\n",
"//\n",
"//val integration = JupyterCommonIntegration()\n",
"//USE(integration.getDefinitions(notebook).first())"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"vf.fragment {\n",
" h1 { +\"AAA\" }\n",
" vision {\n",
" solid {\n",
" ambientLight()\n",
" box(100, 100, 200)\n",
"\n",
" sphere(100) {\n",
" x = 300\n",
" }\n",
" }\n",
" }\n",
"\n",
" vision {\n",
" plotly {\n",
" scatter {\n",
" x(1, 2, 3, 1)\n",
" y(1, 2, 3, 4)\n",
" }\n",
" }\n",
" }\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"jupyter": {
"outputs_hidden": false
},
"tags": []
},
"outputs": [],
"source": [
"Plotly.plot { \n",
" scatter{\n",
" x(1,2,3)\n",
" y(1,2,3)\n",
" }\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Kotlin",
"language": "kotlin",
"name": "kotlin"
},
"ktnbPluginMetadata": {
"isAddProjectLibrariesToClasspath": false
},
"language_info": {
"codemirror_mode": "text/x-kotlin",
"file_extension": ".kt",
"mimetype": "text/x-kotlin",
"name": "kotlin",
"nbconvert_exporter": "",
"pygments_lexer": "kotlin",
"version": "1.8.20"
}
},
"nbformat": 4,
"nbformat_minor": 4
}

View File

@ -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",

View File

@ -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)
}

View File

@ -1,51 +0,0 @@
package space.kscience.visionforge.examples
import org.jetbrains.kotlinx.jupyter.api.libraries.resources
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.gdml.Gdml
import space.kscience.plotly.Plot
import space.kscience.visionforge.gdml.toVision
import space.kscience.visionforge.jupyter.VFIntegrationBase
import space.kscience.visionforge.plotly.PlotlyPlugin
import space.kscience.visionforge.plotly.asVision
import space.kscience.visionforge.solid.Solids
import space.kscience.visionforge.visionManager
@DFExperimental
internal class VisionForgePlayGroundForJupyter : VFIntegrationBase(
Context("VisionForge") {
plugin(Solids)
plugin(PlotlyPlugin)
}.visionManager
) {
override fun Builder.afterLoaded() {
resources {
js("VisionForge") {
classPath("js/visionforge-playground.js")
}
}
import(
"space.kscience.gdml.*",
"space.kscience.plotly.*",
"space.kscience.plotly.models.*",
"space.kscience.visionforge.solid.*",
)
render<Gdml> { gdmlModel ->
handler.produceHtml {
vision { gdmlModel.toVision() }
}
}
render<Plot> { plot ->
handler.produceHtml {
vision { plot.asVision() }
}
}
}
}

View File

@ -0,0 +1,102 @@
package space.kscience.visionforge.examples
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import space.kscience.dataforge.meta.configure
import space.kscience.kmath.complex.Quaternion
import space.kscience.kmath.complex.QuaternionField
import space.kscience.kmath.complex.conjugate
import space.kscience.kmath.geometry.*
import space.kscience.visionforge.solid.*
import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.sin
fun main() = serve {
// val azimuth = 60.degrees
// val inclination = 15.degrees
// val direction = with(QuaternionField) {
// Quaternion.fromRotation(-azimuth, Euclidean3DSpace.zAxis) *
// Quaternion.fromRotation(Angle.piDiv2 - inclination, Euclidean3DSpace.yAxis)
// }
//val direction2 = Quaternion.fromEuler(Angle.zero, Angle.piDiv2 - inclination, -azimuth, RotationOrder.ZYX)
val target = Quaternion.fromEuler((-45).degrees, 45.degrees, Angle.zero, RotationOrder.XYZ)
vision("canvas") {
requirePlugin(Solids)
solid(options = {
configure { "controls.enabled" put false }
}) {
rotationX = -PI / 2
rotationZ = PI
//axes(200)
ambientLight()
val platform = solidGroup("platform") {
cylinder(50, 5, name = "base")
solidGroup("frame") {
z = 60
solidGroup("antenna") {
axes(200)
tube(40, 10, 30)
sphereLayer(100, 95, theta = PI / 6) {
z = 100
rotationX = -PI / 2
}
cylinder(5, 30) {
z = 15
}
sphereLayer(101, 94, phi = PI / 32, theta = PI / 6) {
z = 100
rotationX = -PI / 2
color("red")
}
quaternion = target
}
}
}
val frame = platform["frame"] as SolidGroup
val antenna = frame["antenna"] as SolidGroup
val xPeriod = 5000 //ms
val yPeriod = 7000 //ms
val incRot = Quaternion.fromRotation(30.degrees, Euclidean3DSpace.zAxis)
val rotationJob = context.launch {
var time: Long = 0L
while (isActive) {
with(QuaternionField) {
delay(200)
platform.quaternion = Quaternion.fromRotation(
15.degrees * sin(time.toDouble() * 2 * PI / xPeriod),
Euclidean3DSpace.xAxis
) * Quaternion.fromRotation(
15.degrees * cos(time * 2 * PI / yPeriod),
Euclidean3DSpace.yAxis
)
val qi = platform.quaternion * incRot
antenna.quaternion = qi.conjugate * incRot.conjugate * target
time += 200
//antenna.quaternion = Quaternion.fromRotation(5.degrees, Euclidean3DSpace.zAxis) * antenna.quaternion
}
}
}
}
}
}

View File

@ -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")
}
}
}
}

View File

@ -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)
}
}
}
}
}

View File

@ -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()

View File

@ -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
}

View File

@ -11,7 +11,7 @@ import space.kscience.visionforge.Colors
import space.kscience.visionforge.html.ResourceLocation
import space.kscience.visionforge.solid.Solids
import space.kscience.visionforge.solid.ambientLight
import space.kscience.visionforge.solid.set
import space.kscience.visionforge.solid.invoke
import space.kscience.visionforge.solid.solid
import java.util.zip.ZipInputStream
import kotlin.io.path.Path
@ -26,7 +26,7 @@ private fun Meta.countTypes(): Sequence<String> = sequence {
}
fun main() {
val string = ZipInputStream(TGeoManager::class.java.getResourceAsStream("/root/BM@N_geometry.zip")!!).use {
val string = ZipInputStream(TGeoManager::class.java.getResourceAsStream("/root/geometry_run_7-2076.zip")!!).use {
it.nextEntry
it.readAllBytes().decodeToString()
}
@ -44,9 +44,9 @@ fun main() {
requirePlugin(Solids)
solid {
ambientLight {
color.set(Colors.white)
color(Colors.white)
}
rootGeo(geo,"BM@N", maxLayer = 3, ignoreRootColors = true).also {
rootGeo(geo,"BM@N", ignoreRootColors = true).also {
Path("data/BM@N.vf.json").writeText(Solids.encodeToString(it))
}
}

View File

@ -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,

View File

@ -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)
}

View File

@ -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")
}
}
}

View File

@ -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")

View File

@ -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
}

View File

@ -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

View File

@ -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")
}
}
}

View File

@ -1,14 +1,14 @@
plugins {
id("space.kscience.gradle.mpp")
application
// application
}
kscience {
useCoroutines()
jvm {
withJava()
jvm()
js{
binaries.executable()
}
js()
dependencies {
implementation(projects.visionforgeSolid)
implementation(projects.visionforgeGdml)
@ -19,6 +19,8 @@ kscience {
}
}
application {
mainClass.set("space.kscience.visionforge.solid.demo.FXDemoAppKt")
}
kotlin.explicitApi = null
//application {
// mainClass.set("space.kscience.visionforge.solid.demo.FXDemoAppKt")
//}

View File

@ -4,6 +4,8 @@ import kotlinx.coroutines.*
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.invoke
import space.kscience.dataforge.names.Name
import space.kscience.kmath.geometry.Euclidean3DSpace
import space.kscience.kmath.geometry.radians
import space.kscience.visionforge.Colors
import space.kscience.visionforge.solid.*
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
@ -21,7 +23,7 @@ fun VisionLayout<Solid>.demo(name: String, title: String = name, block: SolidGro
val vision = solids.solidGroup {
block()
ambientLight {
color.set(Colors.white)
color(Colors.white)
}
}
render(Name.parse(name), vision, meta)
@ -47,23 +49,23 @@ fun VisionLayout<Solid>.showcase() {
ambientLight()
box(100.0, 100.0, 100.0) {
z = -110.0
color.set("teal")
color("teal")
}
sphere(50.0) {
x = 110
detail = 16
color.set("red")
color("red")
}
tube(50, height = 10, innerRadius = 25, angle = PI) {
y = 110
detail = 16
rotationX = PI / 4
color.set("blue")
color("blue")
}
sphereLayer(50, 40, theta = PI / 2) {
rotationX = -PI * 3 / 4
z = 110
color.set(Colors.pink)
color(Colors.pink)
}
}
@ -78,7 +80,7 @@ fun VisionLayout<Solid>.showcase() {
visible = false
x = 110.0
//override color for this cube
color.set(1530)
color(1530)
GlobalScope.launch(Dispatchers.Main) {
while (isActive) {
@ -93,7 +95,7 @@ fun VisionLayout<Solid>.showcase() {
val random = Random(111)
while (isActive) {
delay(1000)
group.color.set(random.nextInt(0, Int.MAX_VALUE))
group.color(random.nextInt(0, Int.MAX_VALUE))
}
}
}
@ -103,9 +105,16 @@ fun VisionLayout<Solid>.showcase() {
solidGroup {
x = 200
rotationY = PI / 4
axes(200)
box(100, 100, 100) {
rotationZ = PI / 4
color.set(Colors.red)
rotate((PI / 4).radians, Euclidean3DSpace.zAxis)
GlobalScope.launch(Dispatchers.Main) {
while (isActive) {
delay(100)
rotate((PI/20).radians,Euclidean3DSpace.yAxis)
}
}
color(Colors.red)
}
}
}
@ -118,7 +127,7 @@ fun VisionLayout<Solid>.showcase() {
for (i in 0..100) {
layer(i * 5, 20 * sin(2 * PI / 100 * i), 20 * cos(2 * PI / 100 * i))
}
color.set(Colors.teal)
color(Colors.teal)
rotationX = -PI / 2
}
}
@ -127,13 +136,16 @@ fun VisionLayout<Solid>.showcase() {
sphere(100) {
detail = 32
opacity = 0.4
color.set(Colors.blue)
color(Colors.blue)
}
repeat(20) {
polyline(Point3D(100, 100, 100), Point3D(-100, -100, -100)) {
polyline(
Float32Vector3D(100, 100, 100),
Float32Vector3D(-100, -100, -100)
) {
thickness = 3.0
rotationX = it * PI2 / 20
color.set(Colors.green)
color(Colors.green)
//rotationY = it * PI2 / 20
}
}
@ -147,6 +159,10 @@ fun VisionLayout<Solid>.showcase() {
z = 26
}
}
demo("STL", "STL loaded from URL") {
stl("https://ozeki.hu/attachments/116/Menger_sponge_sample.stl")
}
}
fun VisionLayout<Solid>.showcaseCSG() {
@ -160,7 +176,7 @@ fun VisionLayout<Solid>.showcaseCSG() {
detail = 32
}
material {
color.set(Colors.pink)
color(Colors.pink)
}
}
composite(CompositeType.UNION) {
@ -170,7 +186,7 @@ fun VisionLayout<Solid>.showcaseCSG() {
sphere(50) {
detail = 32
}
color.set("lightgreen")
color("lightgreen")
opacity = 0.7
}
composite(CompositeType.SUBTRACT) {
@ -181,7 +197,7 @@ fun VisionLayout<Solid>.showcaseCSG() {
sphere(50) {
detail = 32
}
color.set("teal")
color("teal")
opacity = 0.7
}
}
@ -192,7 +208,7 @@ fun VisionLayout<Solid>.showcaseCSG() {
detail = 32
}
box(100, 100, 100)
color.set("red")
color("red")
opacity = 0.5
}
}

View File

@ -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()

View File

@ -1,144 +0,0 @@
package space.kscience.visionforge.jupyter
import io.ktor.http.URLProtocol
import io.ktor.server.application.install
import io.ktor.server.cio.CIO
import io.ktor.server.engine.ApplicationEngine
import io.ktor.server.engine.embeddedServer
import io.ktor.server.util.url
import io.ktor.server.websocket.WebSockets
import kotlinx.coroutines.CoroutineScope
import kotlinx.html.*
import kotlinx.html.stream.createHTML
import org.jetbrains.kotlinx.jupyter.api.HTML
import org.jetbrains.kotlinx.jupyter.api.MimeTypedResult
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.ContextAware
import space.kscience.dataforge.context.info
import space.kscience.dataforge.context.logger
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.int
import space.kscience.dataforge.meta.string
import space.kscience.dataforge.names.Name
import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionManager
import space.kscience.visionforge.html.HtmlVisionFragment
import space.kscience.visionforge.html.visionFragment
import space.kscience.visionforge.server.VisionRoute
import space.kscience.visionforge.server.serveVisionData
import space.kscience.visionforge.visionManager
import kotlin.coroutines.CoroutineContext
import kotlin.random.Random
import kotlin.random.nextUInt
internal fun TagConsumer<*>.renderScriptForId(id: String) {
script {
type = "text/javascript"
unsafe { +"VisionForge.renderAllVisionsById(\"$id\");" }
}
}
/**
* A handler class that includes a server and common utilities
*/
public class VFForNotebook(override val context: Context) : ContextAware, CoroutineScope {
public val visionManager: VisionManager = context.visionManager
private var counter = 0
private var engine: ApplicationEngine? = null
public var isolateFragments: Boolean = false
override val coroutineContext: CoroutineContext get() = context.coroutineContext
public fun legacyMode() {
isolateFragments = true
}
public fun isServerRunning(): Boolean = engine != null
public fun html(block: TagConsumer<*>.() -> Unit): MimeTypedResult = HTML(createHTML().apply(block).finalize())
public fun startServer(
host: String = context.properties["visionforge.host"].string ?: "localhost",
port: Int = context.properties["visionforge.port"].int ?: VisionRoute.DEFAULT_PORT,
): MimeTypedResult = html {
if (engine != null) {
p {
style = "color: red;"
+"Stopping current VisionForge server"
}
}
//val connector: EngineConnectorConfig = EngineConnectorConfig(host, port)
engine?.stop(1000, 2000)
engine = context.embeddedServer(CIO, port, host) {
install(WebSockets)
}.start(false)
p {
style = "color: blue;"
+"Starting VisionForge server on http://$host:$port"
}
}
public fun stopServer() {
engine?.apply {
logger.info { "Stopping VisionForge server" }
stop(1000, 2000)
engine = null
}
}
private fun produceHtmlString(
fragment: HtmlVisionFragment,
): String = createHTML().apply {
val id = "fragment[${fragment.hashCode()}/${Random.nextUInt()}]"
div {
this.id = id
val engine = engine
if (engine != null) {
//if server exist, serve dynamically
//server.serveVisionsFromFragment(consumer, "content-${counter++}", fragment)
val cellRoute = "content-${counter++}"
val collector: MutableMap<Name, Vision> = mutableMapOf()
val url = engine.environment.connectors.first().let {
url {
protocol = URLProtocol.WS
host = it.host
port = it.port
pathSegments = listOf(cellRoute, "ws")
}
}
engine.application.serveVisionData(VisionRoute(cellRoute, visionManager), collector)
visionFragment(
visionManager,
embedData = true,
updatesUrl = url,
onVisionRendered = { name, vision -> collector[name] = vision },
fragment = fragment
)
} else {
//if not, use static rendering
visionFragment(visionManager, fragment = fragment)
}
}
renderScriptForId(id)
}.finalize()
public fun produceHtml(isolated: Boolean? = null, fragment: HtmlVisionFragment): MimeTypedResult =
HTML(produceHtmlString(fragment), isolated ?: isolateFragments)
public fun fragment(body: HtmlVisionFragment): MimeTypedResult = produceHtml(fragment = body)
public fun page(body: HtmlVisionFragment): MimeTypedResult = produceHtml(true, body)
public fun form(builder: FORM.() -> Unit): HtmlFormFragment =
HtmlFormFragment("form[${counter++}]", builder = builder)
}

View File

@ -1,96 +0,0 @@
package space.kscience.visionforge.jupyter
import kotlinx.html.*
import kotlinx.html.stream.createHTML
import org.jetbrains.kotlinx.jupyter.api.HTML
import org.jetbrains.kotlinx.jupyter.api.declare
import org.jetbrains.kotlinx.jupyter.api.libraries.JupyterIntegration
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.ContextAware
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionManager
import space.kscience.visionforge.html.*
import kotlin.random.Random
import kotlin.random.nextUInt
/**
* A base class for different Jupyter VF integrations
*/
@DFExperimental
public abstract class VFIntegrationBase(
public val visionManager: VisionManager,
) : JupyterIntegration(), ContextAware {
override val context: Context get() = visionManager.context
protected val handler: VFForNotebook = VFForNotebook(visionManager.context)
protected abstract fun Builder.afterLoaded()
final override fun Builder.onLoaded() {
onLoaded {
declare("VisionForge" to handler, "vf" to handler)
}
onShutdown {
handler.stopServer()
}
import(
"kotlinx.html.*",
"space.kscience.visionforge.html.*"
)
render<HtmlFragment> { fragment ->
handler.produceHtml(fragment = fragment)
}
render<HtmlVisionFragment> { fragment ->
handler.produceHtml(fragment = fragment)
}
render<Vision> { vision ->
handler.produceHtml {
vision(vision)
}
}
render<VisionPage> { page ->
HTML(createHTML().apply {
head {
meta {
charset = "utf-8"
}
page.pageHeaders.values.forEach {
fragment(it)
}
}
body {
val id = "fragment[${page.hashCode()}/${Random.nextUInt()}]"
div {
this.id = id
visionFragment(visionManager, fragment = page.content)
}
renderScriptForId(id)
}
}.finalize(), true)
}
render<HtmlFormFragment> { fragment ->
handler.produceHtml {
if (!handler.isServerRunning()) {
p {
style = "color: red;"
+"The server is not running. Forms are not interactive. Start server with `VisionForge.startServer()."
}
}
fragment(fragment.formBody)
vision(fragment.vision)
}
}
afterLoaded()
}
}

View File

@ -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")
}
```

View File

@ -1,38 +0,0 @@
plugins {
id("space.kscience.gradle.mpp")
}
description = "Jupyter api artifact for GDML rendering"
kscience {
fullStack("js/gdml-jupyter.js",
jsConfig = { useCommonJs() }
) {
commonWebpackConfig {
sourceMaps = false
cssSupport {
enabled.set(false)
}
}
}
commonMain{
implementation(projects.visionforgeSolid)
implementation(projects.jupyter)
}
jvmMain{
implementation(projects.visionforgeGdml)
}
jsMain{
implementation(projects.visionforgeThreejs)
implementation(projects.ui.ring)
}
jupyterLibrary("space.kscience.visionforge.gdml.jupyter.GdmlForJupyter")
}
readme {
maturity = space.kscience.gradle.Maturity.EXPERIMENTAL
}

View File

@ -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)
}

View File

@ -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<Gdml> { gdmlModel ->
handler.produceHtml {
vision { gdmlModel.toVision() }
}
}
}
}

View File

@ -1,3 +0,0 @@
const ringConfig = require('@jetbrains/ring-ui/webpack.config').config;
config.module.rules.push(...ringConfig.module.rules)

View File

@ -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"
)

View File

@ -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<ThreeCanvasProps> = 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())
}
}

View File

@ -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 }
}
}
}
}

View File

@ -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"))

View File

@ -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
}

View File

@ -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)
}

View File

@ -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(

View File

@ -17,7 +17,7 @@ public data class VisionPage(
/**
* Use a script with given [src] as a global header for all pages.
*/
public fun scriptHeader(src: String, block: SCRIPT.() -> Unit = {}): HtmlFragment = {
public fun scriptHeader(src: String, block: SCRIPT.() -> Unit = {}): HtmlFragment = HtmlFragment{
script {
type = "text/javascript"
this.src = src
@ -26,9 +26,9 @@ public data class VisionPage(
}
/**
* Use css with given stylesheet link as a global header for all pages.
* Use css with the given stylesheet link as a global header for all pages.
*/
public fun styleSheetHeader(href: String, block: LINK.() -> Unit = {}): HtmlFragment = {
public fun styleSheetHeader(href: String, block: LINK.() -> Unit = {}): HtmlFragment = HtmlFragment{
link {
rel = "stylesheet"
this.href = href
@ -36,7 +36,7 @@ public data class VisionPage(
}
}
public fun title(title:String): HtmlFragment = {
public fun title(title:String): HtmlFragment = HtmlFragment{
title(title)
}
}

View File

@ -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") {

View File

@ -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 {

View File

@ -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 <T> TagConsumer<T>.vision(

View File

@ -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"

View File

@ -78,7 +78,7 @@ public fun VisionPage.makeFile(
charset = "utf-8"
}
actualHeaders.values.forEach {
fragment(it)
appendFragment(it)
}
}
body {

View File

@ -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) {

View File

@ -6,6 +6,7 @@ import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.plus
import space.kscience.gdml.*
import space.kscience.kmath.geometry.RotationOrder
import space.kscience.visionforge.*
import space.kscience.visionforge.html.VisionOutput
import space.kscience.visionforge.solid.*
@ -206,9 +207,9 @@ private class GdmlLoader(val settings: GdmlLoaderOptions) {
require(solid.planes.size > 1) { "The polyhedron geometry requires at least two planes" }
val baseRadius = solid.planes.first().rmax * lScale
shape {
(0..solid.numsides).forEach {
(0..<solid.numsides).forEach {
val phi = solid.deltaphi * aScale / solid.numsides * it + solid.startphi * aScale
(baseRadius * cos(phi) to baseRadius * sin(phi))
point(baseRadius * cos(phi), baseRadius * sin(phi))
}
}
solid.planes.forEach { plane ->
@ -248,14 +249,14 @@ private class GdmlLoader(val settings: GdmlLoaderOptions) {
val dyBottom = solid.y1.toDouble() / 2
val dyTop = solid.y2.toDouble() / 2
val dz = solid.z.toDouble() / 2
val node1 = Point3D(-dxBottom, -dyBottom, -dz)
val node2 = Point3D(dxBottom, -dyBottom, -dz)
val node3 = Point3D(dxBottom, dyBottom, -dz)
val node4 = Point3D(-dxBottom, dyBottom, -dz)
val node5 = Point3D(-dxTop, -dyTop, dz)
val node6 = Point3D(dxTop, -dyTop, dz)
val node7 = Point3D(dxTop, dyTop, dz)
val node8 = Point3D(-dxTop, dyTop, dz)
val node1 = Float32Vector3D(-dxBottom, -dyBottom, -dz)
val node2 = Float32Vector3D(dxBottom, -dyBottom, -dz)
val node3 = Float32Vector3D(dxBottom, dyBottom, -dz)
val node4 = Float32Vector3D(-dxBottom, dyBottom, -dz)
val node5 = Float32Vector3D(-dxTop, -dyTop, dz)
val node6 = Float32Vector3D(dxTop, -dyTop, dz)
val node7 = Float32Vector3D(dxTop, dyTop, dz)
val node8 = Float32Vector3D(-dxTop, dyTop, dz)
hexagon(node1, node2, node3, node4, node5, node6, node7, node8, name)
}

View File

@ -5,13 +5,17 @@ plugins {
description = "Common visionforge jupyter module"
kscience {
useKtor()
jvm()
js()
jupyterLibrary()
dependencies {
api(projects.visionforgeCore)
}
jvmMain {
dependencies(jvmMain){
api("io.ktor:ktor-server-cio-jvm")
api("io.ktor:ktor-server-websockets-jvm")
api("io.ktor:ktor-server-cors-jvm")
api(projects.visionforgeServer)
}
}

View File

@ -1,6 +1,7 @@
package space.kscience.visionforge.jupyter
import kotlinx.browser.window
import org.w3c.dom.Document
import org.w3c.dom.Element
import space.kscience.dataforge.context.AbstractPlugin
import space.kscience.dataforge.context.Context
@ -13,15 +14,15 @@ import space.kscience.visionforge.renderAllVisionsById
import space.kscience.visionforge.renderAllVisionsIn
@JsExport
public class VFNotebookPlugin : AbstractPlugin() {
public class VFNotebookClient : AbstractPlugin() {
private val client by require(VisionClient)
public fun renderAllVisionsIn(element: Element) {
client.renderAllVisionsIn(element)
}
public fun renderAllVisionsById(id: String) {
client.renderAllVisionsById(id)
public fun renderAllVisionsById(document: Document, id: String) {
client.renderAllVisionsById(document, id)
}
public fun renderAllVisions() {
@ -30,17 +31,18 @@ public class VFNotebookPlugin : AbstractPlugin() {
init {
console.info("Loading VisionForge global hooks")
//register VisionForge in the browser window
window.asDynamic().vf = this
window.asDynamic().VisionForge = this
window.parent.asDynamic().vf = this
window.parent.asDynamic().VisionForge = this
}
@Suppress("NON_EXPORTABLE_TYPE")
override val tag: PluginTag get() = Companion.tag
@Suppress("NON_EXPORTABLE_TYPE")
public companion object : PluginFactory<VFNotebookPlugin> {
override fun build(context: Context, meta: Meta): VFNotebookPlugin = VFNotebookPlugin()
public companion object : PluginFactory<VFNotebookClient> {
override fun build(context: Context, meta: Meta): VFNotebookClient = VFNotebookClient()
override val tag: PluginTag = PluginTag(name = "vision.notebook", group = PluginTag.DATAFORGE_GROUP)
}

View File

@ -0,0 +1,177 @@
package space.kscience.visionforge.jupyter
import io.ktor.http.URLProtocol
import io.ktor.server.application.install
import io.ktor.server.cio.CIO
import io.ktor.server.engine.ApplicationEngine
import io.ktor.server.engine.embeddedServer
import io.ktor.server.util.url
import io.ktor.server.websocket.WebSockets
import kotlinx.coroutines.CoroutineScope
import kotlinx.html.*
import kotlinx.html.stream.createHTML
import org.jetbrains.kotlinx.jupyter.api.*
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.ContextAware
import space.kscience.dataforge.context.info
import space.kscience.dataforge.context.logger
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.names.Name
import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionManager
import space.kscience.visionforge.html.HtmlVisionFragment
import space.kscience.visionforge.html.visionFragment
import space.kscience.visionforge.server.VisionRoute
import space.kscience.visionforge.server.serveVisionData
import kotlin.coroutines.CoroutineContext
import kotlin.random.Random
import kotlin.random.nextUInt
@Suppress("FunctionName")
internal inline fun HTML(isolated: Boolean = false, block: TagConsumer<*>.() -> Unit): MimeTypedResult =
HTML(createHTML().apply(block).finalize(), isolated)
internal fun KotlinKernelHost.displayHtml(block: TagConsumer<*>.() -> Unit) {
display(HTML(false, block), null)
}
public enum class VisionForgeCompatibility {
JUPYTER,
JUPYTER_LAB,
DATALORE,
IDEA
}
/**
* A handler class that includes a server and common utilities
*/
@Suppress("ExtractKtorModule")
public class VisionForge(
public val visionManager: VisionManager,
public val notebook: Notebook,
meta: Meta = Meta.EMPTY,
) : ContextAware, CoroutineScope {
override val context: Context get() = visionManager.context
public val configuration: ObservableMutableMeta = meta.toMutableMeta()
private var counter = 0
private var engine: ApplicationEngine? = null
override val coroutineContext: CoroutineContext get() = context.coroutineContext
public fun isServerRunning(): Boolean = engine != null
public fun getProperty(name: String): TypedMeta<*>? = configuration[name] ?: context.properties[name]
internal fun startServer(
kernel: KotlinKernelHost,
host: String = getProperty("visionforge.host").string ?: "localhost",
port: Int = getProperty("visionforge.port").int ?: VisionRoute.DEFAULT_PORT,
) {
engine?.let {
kernel.displayHtml {
p {
style = "color: red;"
+"Stopping current VisionForge server"
}
}
it.stop(1000, 2000)
}
//val connector: EngineConnectorConfig = EngineConnectorConfig(host, port)
engine = context.embeddedServer(CIO, port, host) {
install(WebSockets)
}.start(false)
kernel.displayHtml {
p {
style = "color: blue;"
+"Starting VisionForge server on port $port"
}
}
}
internal fun stopServer(kernel: KotlinKernelHost) {
engine?.apply {
logger.info { "Stopping VisionForge server" }
stop(1000, 2000)
engine = null
}
kernel.displayHtml {
p {
style = "color: red;"
+"VisionForge server stopped"
}
}
}
internal fun TagConsumer<*>.renderScriptForId(id: String) {
script {
type = "text/javascript"
//language=JavaScript
unsafe { +"parent.VisionForge.renderAllVisionsById(document, \"$id\");" }
}
}
public fun produceHtml(
isolated: Boolean? = null,
fragment: HtmlVisionFragment,
): MimeTypedResult {
val iframeIsolation = isolated ?: when (notebook.jupyterClientType) {
JupyterClientType.DATALORE, JupyterClientType.JUPYTER_NOTEBOOK -> true
else -> false
}
return HTML(
iframeIsolation
) {
val id = "fragment[${fragment.hashCode()}/${Random.nextUInt()}]"
div {
this.id = id
val engine = engine
if (engine != null) {
//if server exist, serve dynamically
//server.serveVisionsFromFragment(consumer, "content-${counter++}", fragment)
val cellRoute = "content-${counter++}"
val collector: MutableMap<Name, Vision> = mutableMapOf()
val url = engine.environment.connectors.first().let {
url {
protocol = URLProtocol.WS
host = it.host
port = it.port
pathSegments = listOf(cellRoute, "ws")
}
}
engine.application.serveVisionData(VisionRoute(cellRoute, visionManager), collector)
visionFragment(
visionManager,
embedData = true,
updatesUrl = url,
onVisionRendered = { name, vision -> collector[name] = vision },
fragment = fragment
)
} else {
//if not, use static rendering
visionFragment(visionManager, fragment = fragment)
}
}
renderScriptForId(id)
}
}
public fun form(builder: FORM.() -> Unit): HtmlFormFragment =
HtmlFormFragment("form[${counter++}]", builder = builder)
}

View File

@ -0,0 +1,133 @@
package space.kscience.visionforge.jupyter
import kotlinx.html.*
import org.jetbrains.kotlinx.jupyter.api.KotlinKernelHost
import org.jetbrains.kotlinx.jupyter.api.MimeTypedResult
import org.jetbrains.kotlinx.jupyter.api.declare
import org.jetbrains.kotlinx.jupyter.api.libraries.JupyterIntegration
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.ContextAware
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionManager
import space.kscience.visionforge.html.*
import kotlin.random.Random
import kotlin.random.nextUInt
/**
* A base class for different Jupyter VF integrations
*/
@DFExperimental
public abstract class VisionForgeIntegration(
public val visionManager: VisionManager,
) : JupyterIntegration(), ContextAware {
override val context: Context get() = visionManager.context
protected abstract fun Builder.afterLoaded(vf: VisionForge)
final override fun Builder.onLoaded() {
val vf: VisionForge = VisionForge(visionManager, notebook)
onLoaded {
val kernel: KotlinKernelHost = this
declare("VisionForge" to vf, "vf" to vf)
vf.startServer(kernel)
vf.configuration.onChange(this) { name ->
if (name.toString() == "visionforge.port") {
kernel.displayHtml {
p { +"Property 'visionforge.port' changed. Restarting server" }
}
vf.startServer(kernel)
}
}
}
onShutdown {
vf.stopServer(this)
}
import(
"kotlinx.html.*",
"space.kscience.visionforge.html.*",
"space.kscience.visionforge.jupyter.*"
)
//
// render<HtmlFragment> { fragment ->
// HTML(fragment.renderToString())
// }
//
// render<HtmlVisionFragment> { fragment ->
// handler.produceHtml(fragment = fragment)
// }
render<Vision> { vision ->
vf.produceHtml {
vision(vision)
}
}
render<VisionPage> { page ->
HTML(true) {
head {
meta {
charset = "utf-8"
}
page.pageHeaders.values.forEach {
appendFragment(it)
}
}
body {
val id = "fragment[${page.hashCode()}/${Random.nextUInt()}]"
div {
this.id = id
visionFragment(visionManager, fragment = page.content)
}
with(vf) {
renderScriptForId(id)
}
}
}
}
render<HtmlFormFragment> { fragment ->
vf.produceHtml {
if (!vf.isServerRunning()) {
p {
style = "color: red;"
+"The server is not running. Forms are not interactive. Start server with `VisionForge.startServer()."
}
}
appendFragment(fragment.formBody)
vision(fragment.vision)
}
}
afterLoaded(vf)
}
}
/**
* Create a fragment without a head to be embedded in the page
*/
@Suppress("UnusedReceiverParameter")
public fun VisionForge.html(body: TagConsumer<*>.() -> Unit): MimeTypedResult = HTML(false, body)
/**
* Create a fragment without a head to be embedded in the page
*/
public fun VisionForge.fragment(body: VisionTagConsumer<*>.() -> Unit): MimeTypedResult = produceHtml(false, body)
/**
* Create a standalone page in the notebook
*/
public fun VisionForge.page(
pageHeaders: Map<String, HtmlFragment> = emptyMap(),
body: VisionTagConsumer<*>.() -> Unit,
): VisionPage = VisionPage(visionManager, pageHeaders, body)

View File

@ -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()
}
}
}
}
public fun VisionForge.form(id: String? = null, builder: FORM.() -> Unit): HtmlFormFragment =
HtmlFormFragment(id, builder)

View File

@ -0,0 +1,42 @@
plugins {
id("space.kscience.gradle.mpp")
}
description = "Jupyter api artifact including all common modules"
kscience {
fullStack(
"js/visionforge-jupyter-common.js",
jsConfig = { useCommonJs() }
) {
commonWebpackConfig {
sourceMaps = false
cssSupport {
enabled.set(false)
}
}
}
dependencies {
api(projects.visionforgeSolid)
api(projects.visionforgePlotly)
api(projects.visionforgeTables)
api(projects.visionforgeMarkdown)
api(projects.visionforgeJupyter)
}
jvmMain {
api(projects.visionforgeGdml)
}
jsMain {
implementation(projects.ui.ring)
implementation(projects.visionforgeThreejs)
}
jupyterLibrary("space.kscience.visionforge.jupyter.JupyterCommonIntegration")
}
readme {
maturity = space.kscience.gradle.Maturity.EXPERIMENTAL
}

View File

@ -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)
}

View File

@ -0,0 +1,90 @@
package space.kscience.visionforge.jupyter
import kotlinx.html.*
import org.jetbrains.kotlinx.jupyter.api.libraries.resources
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.gdml.Gdml
import space.kscience.plotly.Plot
import space.kscience.plotly.PlotlyPage
import space.kscience.plotly.StaticPlotlyRenderer
import space.kscience.tables.*
import space.kscience.visionforge.gdml.toVision
import space.kscience.visionforge.html.HtmlFragment
import space.kscience.visionforge.html.VisionPage
import space.kscience.visionforge.markup.MarkupPlugin
import space.kscience.visionforge.plotly.PlotlyPlugin
import space.kscience.visionforge.plotly.asVision
import space.kscience.visionforge.solid.Solids
import space.kscience.visionforge.tables.TableVisionPlugin
import space.kscience.visionforge.tables.toVision
import space.kscience.visionforge.visionManager
@DFExperimental
public class JupyterCommonIntegration : VisionForgeIntegration(CONTEXT.visionManager) {
override fun Builder.afterLoaded(vf: VisionForge) {
resources {
js("visionforge-common") {
classPath("js/visionforge-jupyter-common.js")
}
}
import(
"space.kscience.gdml.*",
"space.kscience.visionforge.solid.*",
"space.kscience.tables.*",
"space.kscience.dataforge.meta.*",
"space.kscience.plotly.*",
"space.kscience.plotly.models.*",
"space.kscience.visionforge.plotly.plotly"
)
render<Gdml> { gdmlModel ->
vf.produceHtml {
vision { gdmlModel.toVision() }
}
}
render<Table<*>> { table ->
vf.produceHtml {
vision { table.toVision() }
}
}
render<Plot> { plot ->
vf.produceHtml {
vision { plot.asVision() }
}
}
render<PlotlyPage> { plotlyPage ->
val headers = plotlyPage.headers.associate { plotlyFragment ->
plotlyFragment.hashCode().toString(16) to HtmlFragment {
plotlyFragment.visit(this)
}
}
VisionPage(visionManager, headers) {
div{
p { +"Plotly page renderer is not recommended in VisionForge, use `vf.page{}`" }
}
div {
plotlyPage.fragment.render.invoke(this, StaticPlotlyRenderer)
}
}
}
}
public companion object {
private val CONTEXT: Context = Context("Jupyter-common") {
plugin(Solids)
plugin(PlotlyPlugin)
plugin(TableVisionPlugin)
plugin(MarkupPlugin)
}
}
}

View File

@ -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: {}
}
]
}
)

View File

@ -6,10 +6,10 @@ kscience{
useKtor()
dependencies {
api(projects.visionforgeCore)
api("io.ktor:ktor-server-cio")
api("io.ktor:ktor-server-host-common")
api("io.ktor:ktor-server-html-builder")
api("io.ktor:ktor-server-websockets")
implementation("io.ktor:ktor-server-cors")
api("io.ktor:ktor-server-cors")
}
}

View File

@ -2,7 +2,6 @@ package space.kscience.visionforge.server
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.cio.*
import io.ktor.server.engine.*
import io.ktor.server.html.*
import io.ktor.server.http.content.*
@ -58,7 +57,7 @@ public class VisionRoute(
override val context: Context get() = visionManager.context
/**
* Update minimal interval between updates in milliseconds (if there are no updates, push will not happen
* Update the minimal interval between updates in milliseconds (if there are no updates, push will not happen
*/
public var updateInterval: Long by meta.long(300, key = UPDATE_INTERVAL_KEY)
@ -171,8 +170,8 @@ public fun Application.visionPage(
meta {
charset = "utf-8"
}
headers.forEach { header ->
consumer.header()
headers.forEach { headerContent ->
headerContent.appendTo(consumer)
}
}
body {

View File

@ -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) {

View File

@ -28,7 +28,7 @@ public class ColorAccessor(
}
}
public fun Vision.color(
public fun Vision.colorProperty(
propertyName: Name? = null,
): ReadOnlyProperty<Vision, ColorAccessor> = 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()
}

View File

@ -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<Point3D> = (0 until segments).map { i ->
Point3D(r * cos(phiStart + angleStep * i), r * sin(phiStart + angleStep * i), z)
fun shape(r: Float, z: Float): List<Float32Vector3D> = (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)

View File

@ -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<Point3D> = (0 until segments).map { i ->
Point3D(r * cos(phiStart + angleStep * i), r * sin(phiStart + angleStep * i), z)
fun shape(r: Float, z: Float): List<Float32Vector3D> = (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)

View File

@ -7,7 +7,7 @@ import space.kscience.visionforge.setChild
@Serializable
@SerialName("solid.convex")
public class Convex(public val points: List<Point3D>) : SolidBase<Convex>()
public class Convex(public val points: List<Float32Vector3D>) : SolidBase<Convex>()
public inline fun MutableVisionContainer<Solid>.convex(
name: String? = null,
@ -15,10 +15,10 @@ public inline fun MutableVisionContainer<Solid>.convex(
): Convex = ConvexBuilder().apply(action).build().also { setChild(name, it) }
public class ConvexBuilder {
private val points = ArrayList<Point3D>()
private val points = ArrayList<Float32Vector3D>()
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 {

View File

@ -4,23 +4,25 @@ import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import space.kscience.dataforge.meta.MutableMeta
import space.kscience.dataforge.names.Name
import space.kscience.visionforge.*
import space.kscience.kmath.geometry.component1
import space.kscience.kmath.geometry.component2
import space.kscience.visionforge.MutableVisionContainer
import space.kscience.visionforge.VisionBuilder
import space.kscience.visionforge.setChild
import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.sin
public typealias Shape2D = List<Point2D>
public typealias Shape2D = List<Float32Vector2D>
@Serializable
public class Shape2DBuilder(private val points: ArrayList<Point2D> = ArrayList()) {
public class Shape2DBuilder(private val points: ArrayList<Float32Vector2D> = ArrayList()) {
public fun point(x: Number, y: Number) {
points.add(Point2D(x, y))
points.add(Float32Vector2D(x, y))
}
public infix fun Number.to(y: Number): Unit = point(this, y)
public fun build(): Shape2D = points
}
@ -38,7 +40,7 @@ public data class Layer(var x: Float, var y: Float, var z: Float, var scale: Flo
@Serializable
@SerialName("solid.extrude")
public class Extruded(
public val shape: List<Point2D>,
public val shape: List<Float32Vector2D>,
public val layers: List<Layer>,
) : SolidBase<Extruded>(), GeometrySolid {
@ -50,18 +52,18 @@ public class Extruded(
/**
* Expand the shape for specific layers
*/
val layers: List<List<Point3D>> = layers.map { layer ->
val layers: List<List<Float32Vector3D>> = 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<Point3D>
var upperLayer: List<Float32Vector3D>
for (i in (1 until layers.size)) {
upperLayer = layers[i]
@ -94,7 +96,7 @@ public class Extruded(
}
public class ExtrudeBuilder(
public var shape: List<Point2D> = emptyList(),
public var shape: List<Float32Vector2D> = emptyList(),
public var layers: MutableList<Layer> = ArrayList(),
public val properties: MutableMeta = MutableMeta(),
) {

View File

@ -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<Float>
public object Float32Euclidean2DSpace :
GeometrySpace<Float32Vector2D>,
ScaleOperations<Float32Vector2D> {
@Serializable
@SerialName("Float32Vector2D")
private data class Vector2DImpl(
override val x: Float,
override val y: Float,
) : Float32Vector2D
public object VectorSerializer : KSerializer<Float32Vector2D> {
private val proxySerializer = Vector2DImpl.serializer()
override val descriptor: SerialDescriptor get() = proxySerializer.descriptor
override fun deserialize(decoder: Decoder): Float32Vector2D = decoder.decodeSerializableValue(proxySerializer)
override fun serialize(encoder: Encoder, value: Float32Vector2D) {
val vector = value as? Vector2DImpl ?: Vector2DImpl(value.x, value.y)
encoder.encodeSerializableValue(proxySerializer, vector)
}
}
public fun vector(x: Float, y: Float): Float32Vector2D =
Vector2DImpl(x, y)
public fun vector(x: Number, y: Number): Float32Vector2D =
vector(x.toFloat(), y.toFloat())
override val zero: Float32Vector2D by lazy { vector(0f, 0f) }
override fun norm(arg: Float32Vector2D): Double = sqrt(arg.x.pow(2) + arg.y.pow(2)).toDouble()
public fun Float32Vector2D.norm(): Double = norm(this)
override fun Float32Vector2D.unaryMinus(): Float32Vector2D = vector(-x, -y)
override fun Float32Vector2D.distanceTo(other: Float32Vector2D): Double = (this - other).norm()
override fun add(left: Float32Vector2D, right: Float32Vector2D): Float32Vector2D =
vector(left.x + right.x, left.y + right.y)
override fun scale(a: Float32Vector2D, value: Double): Float32Vector2D =
vector(a.x * value, a.y * value)
override fun Float32Vector2D.dot(other: Float32Vector2D): Double =
(x * other.x + y * other.y).toDouble()
public val xAxis: Float32Vector2D = vector(1.0, 0.0)
public val yAxis: Float32Vector2D = vector(0.0, 1.0)
}
public fun Float32Vector2D(x: Number, y: Number): Float32Vector2D = Float32Euclidean2DSpace.vector(x, y)

View File

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

View File

@ -13,17 +13,17 @@ public interface GeometryBuilder<T : Any> {
* @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 <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>)
}
public fun <T : Any> GeometryBuilder<T>.cap(shape: List<Point3D>, normal: Point3D? = null) {
public fun <T : Any> GeometryBuilder<T>.cap(shape: List<Float32Vector3D>, 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()

View File

@ -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 <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>) {
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<Solid>.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<GenericHexagon>(), Hexagon
@VisionBuilder
public inline fun MutableVisionContainer<Solid>.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) }

View File

@ -15,7 +15,7 @@ import space.kscience.visionforge.*
public abstract class LightSource : SolidBase<LightSource>() {
override val descriptor: MetaDescriptor get() = LightSource.descriptor
public val color: ColorAccessor by color(SolidMaterial.COLOR_KEY)
public val color: ColorAccessor by colorProperty(SolidMaterial.COLOR_KEY)
public var intensity: Number by properties.root(includeStyles = false).number(INTENSITY_KEY) { 1.0 }
public companion object {
@ -70,6 +70,6 @@ public fun MutableVisionContainer<Solid>.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)
}

View File

@ -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<MiscSolid>()
@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<Solid>.axes(
size: Number,
name: String = "@axes",
block: AxesSolid.() -> Unit = {},
): AxesSolid = AxesSolid(size.toDouble()).apply(block).also {
setChild(name, it)
}

View File

@ -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<Point3D>) : SolidBase<PolyLine>() {
public class PolyLine(public val points: List<Float32Vector3D>) : SolidBase<PolyLine>() {
//var lineType by string()
public var thickness: Number by properties.root(inherit = false, includeStyles = true).number { DEFAULT_THICKNESS }
@ -19,7 +22,7 @@ public class PolyLine(public val points: List<Point3D>) : SolidBase<PolyLine>()
@VisionBuilder
public fun MutableVisionContainer<Solid>.polyline(
vararg points: Point3D,
vararg points: Float32Vector3D,
name: String? = null,
action: PolyLine.() -> Unit = {},
): PolyLine = PolyLine(points.toList()).apply(action).also { setChild(name, it) }

View File

@ -8,6 +8,9 @@ import space.kscience.dataforge.meta.descriptors.value
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.plus
import space.kscience.kmath.complex.Quaternion
import space.kscience.kmath.complex.QuaternionField
import space.kscience.kmath.geometry.*
import space.kscience.visionforge.*
import space.kscience.visionforge.Vision.Companion.VISIBLE_KEY
import space.kscience.visionforge.solid.Solid.Companion.DETAIL_KEY
@ -58,8 +61,6 @@ public interface Solid : Vision {
public val ROTATION_KEY: Name = "rotation".asName()
public val QUATERNION_KEY: Name = "quaternion".asName()
public val X_ROTATION_KEY: Name = ROTATION_KEY + X_KEY
public val Y_ROTATION_KEY: Name = ROTATION_KEY + Y_KEY
public val Z_ROTATION_KEY: Name = ROTATION_KEY + Z_KEY
@ -122,15 +123,6 @@ public var Solid.layer: Int
// Common properties
public enum class RotationOrder {
XYZ,
YZX,
ZXY,
XZY,
YXZ,
ZYX
}
/**
* Rotation order
*/
@ -174,18 +166,21 @@ internal fun point(
defaultX: Float,
defaultY: Float = defaultX,
defaultZ: Float = defaultX,
): ReadWriteProperty<Solid, Point3D?> =
object : ReadWriteProperty<Solid, Point3D?> {
override fun getValue(thisRef: Solid, property: KProperty<*>): Point3D? {
): ReadWriteProperty<Solid, Float32Vector3D?> =
object : ReadWriteProperty<Solid, Float32Vector3D?> {
override fun getValue(thisRef: Solid, property: KProperty<*>): Float32Vector3D? {
val item = thisRef.properties.own?.get(name) ?: return null
return object : Point3D {
//using dynamic property accessor because values could change
return object : Float32Vector3D {
override val x: Float get() = item[X_KEY]?.float ?: defaultX
override val y: Float get() = item[Y_KEY]?.float ?: defaultY
override val z: Float get() = item[Z_KEY]?.float ?: defaultZ
override fun toString(): String = item.toString()
}
}
override fun setValue(thisRef: Solid, property: KProperty<*>, value: Point3D?) {
override fun setValue(thisRef: Solid, property: KProperty<*>, value: Float32Vector3D?) {
if (value == null) {
thisRef.properties.setProperty(name, null)
} else {
@ -196,9 +191,9 @@ internal fun point(
}
}
public var Solid.position: Point3D? by point(POSITION_KEY, 0f)
public var Solid.rotation: Point3D? by point(ROTATION_KEY, 0f)
public var Solid.scale: Point3D? by point(SCALE_KEY, 1f)
public var Solid.position: Float32Vector3D? by point(POSITION_KEY, 0f)
public var Solid.rotation: Float32Vector3D? by point(ROTATION_KEY, 0f)
public var Solid.scale: Float32Vector3D? by point(SCALE_KEY, 1f)
public var Solid.x: Number by float(X_POSITION_KEY, 0f)
public var Solid.y: Number by float(Y_POSITION_KEY, 0f)
@ -208,33 +203,49 @@ public var Solid.rotationX: Number by float(X_ROTATION_KEY, 0f)
public var Solid.rotationY: Number by float(Y_ROTATION_KEY, 0f)
public var Solid.rotationZ: Number by float(Z_ROTATION_KEY, 0f)
public var Solid.quaternion: Pair<Float, Point3D>?
get() = properties.getValue(Solid.QUATERNION_KEY)?.list?.let {
/**
* Raw quaternion value defined in properties
*/
public var Solid.quaternionOrNull: Quaternion?
get() = properties.getValue(ROTATION_KEY)?.list?.let {
require(it.size == 4) { "Quaternion must be a number array of 4 elements" }
it[0].float to Point3D(it[1].float, it[2].float, it[3].float)
Quaternion(it[0].float, it[1].float, it[2].float, it[3].float)
}
set(value) {
properties.setValue(
Solid.QUATERNION_KEY,
ROTATION_KEY,
value?.let {
ListValue(
value.first,
value.second.x,
value.second.y,
value.second.z
value.w,
value.x,
value.y,
value.z
)
}
)
}
//public var Solid.quaternion: Quaternion?
// get() = meta[Solid::quaternion.name]?.value?.doubleArray?.let { Quaternion(it) }
// set(value) {
// meta[Solid::quaternion.name] = value?.values?.asValue()
// }
/**
* Quaternion value including information from euler angles
*/
public var Solid.quaternion: Quaternion
get() = quaternionOrNull ?: Quaternion.fromEuler(
rotationX.radians,
rotationY.radians,
rotationZ.radians,
rotationOrder
)
set(value) {
quaternionOrNull = value
}
public var Solid.scaleX: Number by float(X_SCALE_KEY, 1f)
public var Solid.scaleY: Number by float(Y_SCALE_KEY, 1f)
public var Solid.scaleZ: Number by float(Z_SCALE_KEY, 1f)
public var Solid.scaleZ: Number by float(Z_SCALE_KEY, 1f)
/**
* Add rotation with given [angle] relative to given [axis]
*/
public fun Solid.rotate(angle: Angle, axis: DoubleVector3D): Unit = with(QuaternionField) {
quaternion = Quaternion.fromRotation(angle, axis)*quaternion
}

View File

@ -17,7 +17,7 @@ import space.kscience.visionforge.VisionBuilder
*/
public interface PrototypeHolder {
/**
* Build or update prototype tree
* Build or update the prototype tree
*/
@VisionBuilder
public fun prototypes(builder: MutableVisionContainer<Solid>.() -> Unit)
@ -43,6 +43,9 @@ public class SolidGroup : AbstractVisionGroup(), Solid, PrototypeHolder, Mutable
it to value
}.toMap()
/**
* Get a child solid with given relative [name] if it exists
*/
public operator fun get(name: Name): Solid? = children.getChild(name) as? Solid
private var prototypes: SolidGroup?
@ -84,6 +87,8 @@ public class SolidGroup : AbstractVisionGroup(), Solid, PrototypeHolder, Mutable
}
}
public operator fun SolidGroup.get(name:String): Solid? = get(name.parseAsName())
@VisionBuilder
public inline fun MutableVisionContainer<Solid>.solidGroup(
name: Name? = null,
@ -99,3 +104,8 @@ public inline fun MutableVisionContainer<Solid>.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)

View File

@ -2,7 +2,9 @@ package space.kscience.visionforge.solid
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@ -61,7 +63,7 @@ public class SolidReference(
}
override fun getValue(name: Name, inherit: Boolean?, includeStyles: Boolean?): Value? {
if(name == Vision.STYLE_KEY){
if (name == Vision.STYLE_KEY) {
return buildList {
properties?.getValue(Vision.STYLE_KEY)?.list?.forEach {
add(it)
@ -90,7 +92,7 @@ public class SolidReference(
prototype.getStyleProperty(name)?.value?.let { return it }
}
if(inheritFlag){
if (inheritFlag) {
//5. own inheritance
parent?.properties?.getValue(name, inheritFlag, includeStyles)?.let { return it }
//6. prototype inheritance
@ -226,13 +228,13 @@ internal class SolidReferenceChild(
*/
public fun MutableVisionContainer<Solid>.ref(
templateName: Name,
name: String? = null,
name: Name? = null,
): SolidReference = SolidReference(templateName).also { setChild(name, it) }
public fun MutableVisionContainer<Solid>.ref(
templateName: String,
name: String? = null,
): SolidReference = ref(Name.parse(templateName), name)
templateName: Name,
name: String,
): SolidReference = ref(templateName, name.parseAsName())
/**
* Add new [SolidReference] wrapping given object and automatically adding it to the prototypes.
@ -251,7 +253,7 @@ public fun SolidGroup.newRef(
} else if (existing != obj) {
error("Can't add different prototype on top of existing one")
}
return children.ref(prototypeName, name)
return children.ref(prototypeName, name?.parseAsName())
}

View File

@ -48,9 +48,12 @@ public class Solids(meta: Meta) : VisionPlugin(meta), MutableVisionContainer<Sol
subclass(AmbientLightSource.serializer())
subclass(PointLightSource.serializer())
subclass(AxesSolid.serializer())
}
public val serializersModuleForSolids: SerializersModule = SerializersModule {
polymorphic(Vision::class) {
subclass(SimpleVisionGroup.serializer())
solids()
@ -91,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)

View File

@ -20,12 +20,12 @@ public class Sphere(
) : SolidBase<Sphere>(), GeometrySolid {
override fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>) {
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

View File

@ -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

View File

@ -0,0 +1,25 @@
package space.kscience.visionforge.solid
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import space.kscience.visionforge.MutableVisionContainer
import space.kscience.visionforge.VisionBuilder
import space.kscience.visionforge.setChild
public sealed class StlSolid: SolidBase<StlSolid>()
@Serializable
@SerialName("solid.stl.url")
public class StlUrlSolid(public val url: String) : StlSolid()
@Serializable
@SerialName("solid.stl.binary")
public class StlBinarySolid(public val data: ByteArray) : StlSolid()
@VisionBuilder
public inline fun MutableVisionContainer<Solid>.stl(
url: String,
name: String? = null,
action: StlSolid.() -> Unit = {},
): StlSolid = StlUrlSolid(url).apply(action).also { setChild(name, it) }

View File

@ -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<Point3D> {
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

View File

@ -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>(::AxesScheme) {
public const val AXIS_SIZE: Double = 1000.0
public const val AXIS_WIDTH: Double = 3.0

View File

@ -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>(::Canvas3DOptions) {
override val descriptor: MetaDescriptor by lazy {
MetaDescriptor {
@Suppress("DEPRECATION")
scheme(Canvas3DOptions::axes, AxesScheme)
value(Canvas3DOptions::layers) {

View File

@ -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

View File

@ -18,7 +18,7 @@ class CompositeTest {
detail = 32
}
material {
color.set("pink")
color("pink")
}
}
}

View File

@ -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 {

Some files were not shown because too many files have changed in this diff Show More