Fix plotly renderer and resource serialization

This commit is contained in:
Alexander Nozik 2021-02-24 13:42:55 +03:00
parent 60906db32e
commit 5d0ceb8e50
18 changed files with 269 additions and 83 deletions

View File

@ -3,23 +3,24 @@ package hep.dataforge.playground
import hep.dataforge.context.Context import hep.dataforge.context.Context
import hep.dataforge.vision.VisionManager import hep.dataforge.vision.VisionManager
import hep.dataforge.vision.gdml.gdml import hep.dataforge.vision.gdml.gdml
import hep.dataforge.vision.html.Page import hep.dataforge.vision.html.*
import hep.dataforge.vision.html.embedVisionFragment
import hep.dataforge.vision.html.fragment
import hep.dataforge.vision.html.scriptHeader
import hep.dataforge.vision.plotly.PlotlyPlugin import hep.dataforge.vision.plotly.PlotlyPlugin
import hep.dataforge.vision.plotly.VisionOfPlotly import hep.dataforge.vision.plotly.VisionOfPlotly
import hep.dataforge.vision.solid.SolidManager import hep.dataforge.vision.solid.SolidManager
import hep.dataforge.vision.solid.solid import hep.dataforge.vision.solid.solid
import hep.dataforge.vision.visionManager import hep.dataforge.vision.visionManager
import kotlinx.html.div import kotlinx.html.div
import kotlinx.html.id
import kotlinx.html.script
import kotlinx.html.stream.createHTML import kotlinx.html.stream.createHTML
import kotlinx.html.unsafe
import kscience.plotly.Plot import kscience.plotly.Plot
import kscience.plotly.PlotlyFragment import kscience.plotly.PlotlyFragment
import org.jetbrains.kotlinx.jupyter.api.HTML import org.jetbrains.kotlinx.jupyter.api.HTML
import org.jetbrains.kotlinx.jupyter.api.Notebook import org.jetbrains.kotlinx.jupyter.api.Notebook
import org.jetbrains.kotlinx.jupyter.api.annotations.JupyterLibrary import org.jetbrains.kotlinx.jupyter.api.annotations.JupyterLibrary
import org.jetbrains.kotlinx.jupyter.api.libraries.* import org.jetbrains.kotlinx.jupyter.api.libraries.*
import org.jetbrains.kotlinx.jupyter.api.libraries.ResourceLocation
import space.kscience.gdml.Gdml import space.kscience.gdml.Gdml
@JupyterLibrary @JupyterLibrary
@ -34,16 +35,25 @@ internal class VisionForgePlayGroundForJupyter : JupyterIntegration() {
ResourcePathType.CLASSPATH_PATH))) ResourcePathType.CLASSPATH_PATH)))
val jsResource = LibraryResource(name = "VisionForge", type = ResourceType.JS, bundles = listOf(jsBundle)) val jsResource = LibraryResource(name = "VisionForge", type = ResourceType.JS, bundles = listOf(jsBundle))
private var counter = 0
private fun produceHtmlVisionString(fragment: HtmlVisionFragment) = createHTML().div {
val id = "visionforge.vision[${counter++}]"
div {
this.id = id
embedVisionFragment(context.visionManager, fragment = fragment)
}
script {
type = "text/javascript"
unsafe { +"window.renderVisionsAt(\"$id\");" }
}
}
override fun Builder.onLoaded(notebook: Notebook?) { override fun Builder.onLoaded(notebook: Notebook?) {
resource(jsResource) resource(jsResource)
import("space.kscience.gdml.*", "kscience.plotly.*", "kscience.plotly.models.*") import("space.kscience.gdml.*", "kscience.plotly.*", "kscience.plotly.models.*")
onLoaded {
val header = scriptHeader("js/visionforge-playground.js", null, hep.dataforge.vision.html.ResourceLocation.EMBED)
display(HTML(createHTML().apply(header).finalize()))
}
render<Gdml> { gdmlModel -> render<Gdml> { gdmlModel ->
val fragment = VisionManager.fragment { val fragment = VisionManager.fragment {
vision { vision {
@ -53,11 +63,7 @@ internal class VisionForgePlayGroundForJupyter : JupyterIntegration() {
} }
} }
val html = createHTML().div { HTML(produceHtmlVisionString(fragment))
embedVisionFragment(context.visionManager, fragment = fragment)
}
HTML(html)
} }
render<Plot> { plot -> render<Plot> { plot ->
@ -67,11 +73,7 @@ internal class VisionForgePlayGroundForJupyter : JupyterIntegration() {
} }
} }
val html = createHTML().div { HTML( produceHtmlVisionString(fragment))
embedVisionFragment(context.visionManager, fragment = fragment)
}
HTML(html)
} }
render<kscience.plotly.HtmlFragment> { fragment -> render<kscience.plotly.HtmlFragment> { fragment ->

View File

@ -1,10 +1,14 @@
//import hep.dataforge.vision.plotly.withPlotly //import hep.dataforge.vision.plotly.withPlotly
import hep.dataforge.vision.plotly.withPlotly import hep.dataforge.vision.plotly.withPlotly
import hep.dataforge.vision.renderVisionsAt
import hep.dataforge.vision.renderVisionsInWindow import hep.dataforge.vision.renderVisionsInWindow
import hep.dataforge.vision.solid.three.loadThreeJs import hep.dataforge.vision.solid.three.withThreeJs
import kotlinx.browser.window
fun main() { fun main() {
withPlotly() withPlotly()
loadThreeJs() withThreeJs()
renderVisionsInWindow() renderVisionsInWindow()
window.asDynamic()["renderVisionsInWindow"] = ::renderVisionsInWindow
window.asDynamic()["renderVisionsAt"] = ::renderVisionsAt
} }

View File

@ -0,0 +1,74 @@
package hep.dataforge.vision.examples
import hep.dataforge.misc.DFExperimental
import hep.dataforge.vision.VisionForge
import hep.dataforge.vision.VisionManager
import hep.dataforge.vision.gdml.gdml
import hep.dataforge.vision.html.ResourceLocation
import hep.dataforge.vision.html.fragment
import hep.dataforge.vision.solid.solid
import hep.dataforge.vision.solid.withSolids
import space.kscience.gdml.*
internal val cubes = Gdml {
val center = define.position("center")
structure {
val air = ref<GdmlMaterial>("G4_AIR")
val tubeMaterial = ref<GdmlMaterial>("tube")
val boxMaterial = ref<GdmlMaterial>("box")
val segment = solids.tube("segment", 20, 5.0) {
rmin = 17
deltaphi = 60
aunit = AUnit.DEG.title
}
val worldBox = solids.box("largeBox", 200, 200, 200)
val smallBox = solids.box("smallBox", 30, 30, 30)
val segmentVolume = volume("segment", tubeMaterial, segment.ref()) {}
val circle = volume("composite", boxMaterial, smallBox.ref()) {
for (i in 0 until 6) {
physVolume(segmentVolume) {
positionref = center.ref()
rotation {
z = 60 * i
unit = AUnit.DEG.title
}
}
}
}
world = volume("world", air, worldBox.ref()) {
for (i in 0 until 3) {
for (j in 0 until 3) {
for (k in 0 until 3) {
physVolume(circle) {
position {
x = (-50 + i * 50)
y = (-50 + j * 50)
z = (-50 + k * 50)
}
rotation {
x = i * 120
y = j * 120
z = 120 * k
}
}
}
}
}
}
}
}
@DFExperimental
fun main() {
val content = VisionManager.fragment {
vision("canvas") {
solid {
gdml(cubes)
}
}
}
VisionForge.withSolids().makeVisionFile(content, resourceLocation = ResourceLocation.SYSTEM)
}

View File

@ -3,6 +3,7 @@ package hep.dataforge.vision.examples
import hep.dataforge.misc.DFExperimental import hep.dataforge.misc.DFExperimental
import hep.dataforge.vision.VisionForge import hep.dataforge.vision.VisionForge
import hep.dataforge.vision.VisionManager import hep.dataforge.vision.VisionManager
import hep.dataforge.vision.html.ResourceLocation
import hep.dataforge.vision.html.fragment import hep.dataforge.vision.html.fragment
import hep.dataforge.vision.plotly.plotly import hep.dataforge.vision.plotly.plotly
import hep.dataforge.vision.plotly.withPlotly import hep.dataforge.vision.plotly.withPlotly
@ -21,5 +22,5 @@ fun main() {
} }
} }
VisionForge.withPlotly().makeVisionFile(fragment) VisionForge.withPlotly().makeVisionFile(fragment, resourceLocation = ResourceLocation.SYSTEM)
} }

View File

@ -9,7 +9,7 @@ import hep.dataforge.vision.solid.box
import hep.dataforge.vision.solid.solid import hep.dataforge.vision.solid.solid
import hep.dataforge.vision.solid.withSolids import hep.dataforge.vision.solid.withSolids
@OptIn(DFExperimental::class) @DFExperimental
fun main() { fun main() {
val content = VisionManager.fragment { val content = VisionManager.fragment {
vision("canvas") { vision("canvas") {

View File

@ -83,7 +83,7 @@ public abstract class VisionTagConsumer<R>(
visionProvider: VisionOutput.() -> Vision, visionProvider: VisionOutput.() -> Vision,
): T = vision(name.toName(), visionProvider) ): T = vision(name.toName(), visionProvider)
public inline fun <T> TagConsumer<T>.vision( public fun <T> TagConsumer<T>.vision(
vision: Vision, vision: Vision,
): T = vision("vision[${vision.hashCode()}]".toName(), vision) ): T = vision("vision[${vision.hashCode()}]".toName(), vision)

View File

@ -4,19 +4,32 @@ import hep.dataforge.context.Context
import hep.dataforge.context.Global import hep.dataforge.context.Global
import hep.dataforge.vision.client.VisionClient import hep.dataforge.vision.client.VisionClient
import hep.dataforge.vision.client.renderAllVisions import hep.dataforge.vision.client.renderAllVisions
import hep.dataforge.vision.client.renderAllVisionsAt
import kotlinx.browser.document
import kotlinx.browser.window import kotlinx.browser.window
public actual val VisionForge: Context = Global.context("VisionForge").apply{ public actual val VisionForge: Context = Global.context("VisionForge").apply {
plugins.fetch(VisionManager) plugins.fetch(VisionManager)
plugins.fetch(VisionClient) plugins.fetch(VisionClient)
} }
/** /**
* Render all visions in this [window] using current global state of [VisionForge] * Render all visions in this [window] using current global state of [VisionForge]
*/ */
@JsExport @JsExport
public fun renderVisionsInWindow(){ public fun renderVisionsInWindow() {
window.onload = { window.onload = {
VisionForge.plugins[VisionClient]?.renderAllVisions() VisionForge.plugins[VisionClient]?.renderAllVisions()
} }
} }
@JsExport
public fun renderVisionsAt(id: String) {
val element = document.getElementById(id)
if (element != null) {
VisionForge.plugins[VisionClient]?.renderAllVisionsAt(element)
} else {
console.warn("Element with id $id not found")
}
}

View File

@ -43,8 +43,16 @@ public class VisionClient : AbstractPlugin() {
private fun getRenderers() = context.gather<ElementVisionRenderer>(ElementVisionRenderer.TYPE).values private fun getRenderers() = context.gather<ElementVisionRenderer>(ElementVisionRenderer.TYPE).values
private fun findRendererFor(vision: Vision): ElementVisionRenderer? = private fun findRendererFor(vision: Vision): ElementVisionRenderer? {
getRenderers().maxByOrNull { it.rateVision(vision) } return getRenderers().mapNotNull {
val rating = it.rateVision(vision)
if (rating > 0) {
rating to it
} else {
null
}
}.maxByOrNull { it.first }?.second
}
private fun Element.getEmbeddedData(className: String): String? = getElementsByClassName(className)[0]?.innerHTML private fun Element.getEmbeddedData(className: String): String? = getElementsByClassName(className)[0]?.innerHTML

View File

@ -8,6 +8,10 @@ import kotlinx.html.unsafe
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.StandardOpenOption import java.nio.file.StandardOpenOption
import java.security.MessageDigest
import kotlin.io.path.ExperimentalPathApi
import kotlin.io.path.readText
/** /**
* The location of resources for plot. * The location of resources for plot.
@ -37,21 +41,29 @@ public enum class ResourceLocation {
internal const val VISIONFORGE_ASSETS_PATH = ".dataforge/vision/assets" internal const val VISIONFORGE_ASSETS_PATH = ".dataforge/vision/assets"
private fun ByteArray.toHexString() = asUByteArray().joinToString("") { it.toString(16).padStart(2, '0') }
/** /**
* Check if the asset exists in given local location and put it there if it does not * Check if the asset exists in given local location and put it there if it does not
* @param * @param
*/ */
@OptIn(ExperimentalPathApi::class)
internal fun checkOrStoreFile(htmlPath: Path, filePath: Path, resource: String): Path { internal fun checkOrStoreFile(htmlPath: Path, filePath: Path, resource: String): Path {
//TODO add logging
val fullPath = htmlPath.resolveSibling(filePath).toAbsolutePath().resolve(resource) val fullPath = htmlPath.resolveSibling(filePath).toAbsolutePath().resolve(resource)
if (Files.exists(fullPath)) {
//TODO checksum
} else {
//TODO add logging
val bytes = VisionManager::class.java.getResourceAsStream("/$resource").readAllBytes() val bytes = VisionManager::class.java.getResourceAsStream("/$resource").readAllBytes()
val md = MessageDigest.getInstance("MD5")
val checksum = md.digest(bytes).toHexString()
val md5File = fullPath.resolveSibling(fullPath.fileName.toString() + ".md5")
val skip: Boolean = Files.exists(fullPath) && Files.exists(md5File) && md5File.readText() == checksum
if (!skip) {
Files.createDirectories(fullPath.parent) Files.createDirectories(fullPath.parent)
Files.write(fullPath, bytes, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE) Files.write(fullPath, bytes, StandardOpenOption.CREATE, StandardOpenOption.WRITE)
Files.write(md5File, checksum.encodeToByteArray(), StandardOpenOption.CREATE, StandardOpenOption.WRITE)
} }
return if (htmlPath.isAbsolute && fullPath.startsWith(htmlPath.parent)) { return if (htmlPath.isAbsolute && fullPath.startsWith(htmlPath.parent)) {

View File

@ -1,38 +1,36 @@
package hep.dataforge.vision.gdml package hep.dataforge.vision.gdml
import hep.dataforge.names.toName
import hep.dataforge.vision.solid.SolidManager import hep.dataforge.vision.solid.SolidManager
import space.kscience.gdml.Gdml import space.kscience.gdml.Gdml
import nl.adaptivity.xmlutil.StAXReader
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import space.kscience.gdml.decodeFromStream
import kotlin.test.assertNotNull
class TestConvertor { class TestConvertor {
@Test @Test
fun testBMNGeometry() { fun testBMNGeometry() {
val stream = javaClass.getResourceAsStream("/gdml/BM@N.gdml") val stream = javaClass.getResourceAsStream("/gdml/BM@N.gdml")
val xmlReader = StAXReader(stream, "UTF-8") val gdml = Gdml.decodeFromStream(stream)
val xml = Gdml.format.parse(Gdml.serializer(), xmlReader) val vision = gdml.toVision()
val vision = xml.toVision() //println(SolidManager.encodeToString(vision))
println(SolidManager.encodeToString(vision))
} }
@Test @Test
fun testCubes() { fun testCubes() {
val stream = javaClass.getResourceAsStream("/gdml/cubes.gdml") val stream = javaClass.getResourceAsStream("/gdml/cubes.gdml")
val gdml = Gdml.decodeFromStream(stream)
val xmlReader = StAXReader(stream, "UTF-8") val vision = gdml.toVision()
val xml = Gdml.format.parse(Gdml.serializer(), xmlReader) assertNotNull(vision.getPrototype("solids.box".toName()))
val visual = xml.toVision() //println(SolidManager.encodeToString(vision))
// println(visual)
} }
@Test @Test
fun testSimple() { fun testSimple() {
val stream = javaClass.getResourceAsStream("/gdml/simple1.gdml") val stream = javaClass.getResourceAsStream("/gdml/simple1.gdml")
val gdml = Gdml.decodeFromStream(stream)
val xmlReader = StAXReader(stream, "UTF-8") val vision = gdml.toVision()
val xml = Gdml.format.parse(Gdml.serializer(), xmlReader) //println(SolidManager.encodeToString(vision))
val vision = xml.toVision()
println(SolidManager.encodeToString(vision))
} }
} }

View File

@ -0,0 +1,74 @@
package hep.dataforge.vision.gdml
import hep.dataforge.names.toName
import hep.dataforge.vision.solid.SolidGroup
import hep.dataforge.vision.solid.SolidManager
import org.junit.jupiter.api.Test
import space.kscience.gdml.*
import kotlin.test.assertNotNull
class TestCubes {
internal val cubes = Gdml {
val center = define.position("center")
structure {
val air = ref<GdmlMaterial>("G4_AIR")
val tubeMaterial = ref<GdmlMaterial>("tube")
val boxMaterial = ref<GdmlMaterial>("box")
val segment = solids.tube("segment", 20, 5.0) {
rmin = 17
deltaphi = 60
aunit = AUnit.DEG.title
}
val worldBox = solids.box("largeBox", 200, 200, 200)
val smallBox = solids.box("smallBox", 30, 30, 30)
val segmentVolume = volume("segment", tubeMaterial, segment.ref()) {}
val circle = volume("composite", boxMaterial, smallBox.ref()) {
for (i in 0 until 6) {
physVolume(segmentVolume) {
positionref = center.ref()
rotation {
z = 60 * i
unit = AUnit.DEG.title
}
}
}
}
world = volume("world", air, worldBox.ref()) {
for (i in 0 until 3) {
for (j in 0 until 3) {
for (k in 0 until 3) {
physVolume(circle) {
position {
x = (-50 + i * 50)
y = (-50 + j * 50)
z = (-50 + k * 50)
}
rotation {
x = i * 120
y = j * 120
z = 120 * k
}
}
}
}
}
}
}
}
@Test
fun testCubesDirect(){
val vision = cubes.toVision()
assertNotNull(vision.getPrototype("solids.smallBox".toName()))
}
@Test
fun testCubesReSerialize(){
val vision = cubes.toVision()
val serialized = SolidManager.encodeToString(vision)
val deserialized = SolidManager.decodeFromString(serialized) as SolidGroup
assertNotNull(deserialized.getPrototype("solids.smallBox".toName()))
}
}

View File

@ -2,6 +2,8 @@ package hep.dataforge.vision.plotly
import hep.dataforge.context.* import hep.dataforge.context.*
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.names.Name
import hep.dataforge.names.asName
import hep.dataforge.vision.Vision import hep.dataforge.vision.Vision
import hep.dataforge.vision.VisionForge import hep.dataforge.vision.VisionForge
import hep.dataforge.vision.VisionPlugin import hep.dataforge.vision.VisionPlugin
@ -26,11 +28,18 @@ public actual class PlotlyPlugin : VisionPlugin(), ElementVisionRenderer {
} }
override fun render(element: Element, vision: Vision, meta: Meta) { override fun render(element: Element, vision: Vision, meta: Meta) {
val plot = (vision as? VisionOfPlotly)?.plot ?: error("Only VisionOfPlotly visions are supported") val plot = (vision as? VisionOfPlotly)?.plot ?: error("VisionOfPlotly expected but ${vision::class} found")
val config = PlotlyConfig.read(meta) val config = PlotlyConfig.read(meta)
element.plot(plot, config) element.plot(plot, config)
} }
override fun content(target: String): Map<Name, Any> {
return when (target) {
ElementVisionRenderer.TYPE -> mapOf("plotly".asName() to this)
else -> super.content(target)
}
}
public companion object : PluginFactory<PlotlyPlugin> { public companion object : PluginFactory<PlotlyPlugin> {
override val tag: PluginTag = PluginTag("vision.plotly", PluginTag.DATAFORGE_GROUP) override val tag: PluginTag = PluginTag("vision.plotly", PluginTag.DATAFORGE_GROUP)
override val type: KClass<PlotlyPlugin> = PlotlyPlugin::class override val type: KClass<PlotlyPlugin> = PlotlyPlugin::class

View File

@ -35,27 +35,6 @@ public class Box(
geometryBuilder.face4(node8, node5, node6, node7) geometryBuilder.face4(node8, node5, node6, node7)
} }
// override fun equals(other: Any?): Boolean {
// if (this === other) return true
// if (other == null || this::class != other::class) return false
//
// other as Box
//
// if (xSize != other.xSize) return false
// if (ySize != other.ySize) return false
// if (zSize != other.zSize) return false
//
// return solidEquals(this, other)
// }
//
// override fun hashCode(): Int {
// var result = xSize.hashCode()
// result = 31 * result + ySize.hashCode()
// result = 31 * result + zSize.hashCode()
// return 31 * result + Solid.solidHashCode(this)
// }
public companion object { public companion object {
} }

View File

@ -28,9 +28,13 @@ public interface PrototypeHolder {
@Serializable @Serializable
@SerialName("group.solid") @SerialName("group.solid")
public class SolidGroup( public class SolidGroup(
@Serializable(Prototypes.Companion::class) @SerialName("prototypes") private var prototypes: MutableVisionGroup? = null, @Serializable(Prototypes.Companion::class) @SerialName("prototypes") internal var prototypes: MutableVisionGroup? = null,
) : VisionGroupBase(), Solid, PrototypeHolder { ) : VisionGroupBase(), Solid, PrototypeHolder {
init {
prototypes?.parent = this
}
override val descriptor: NodeDescriptor get() = Solid.descriptor override val descriptor: NodeDescriptor get() = Solid.descriptor
/** /**

View File

@ -8,21 +8,21 @@ import kotlin.test.assertEquals
class SolidPluginTest { class SolidPluginTest {
val vision = SolidGroup { val vision = SolidGroup {
box(100,100,100, name = "aBox") box(100, 100, 100, name = "aBox")
sphere(100,name = "aSphere"){ sphere(100, name = "aSphere") {
z = 200 z = 200
} }
} }
@DFExperimental @DFExperimental
@Test @Test
fun testPluginConverter(){ fun testPluginConverter() {
val visionManager = Global.plugins.fetch(SolidManager).visionManager val visionManager = Global.plugins.fetch(SolidManager).visionManager
val meta = visionManager.encodeToMeta(vision) val meta = visionManager.encodeToMeta(vision)
val reconstructed = visionManager.decodeFromMeta(meta) as SolidGroup val reconstructed = visionManager.decodeFromMeta(meta) as SolidGroup
assertEquals(vision["aBox"],reconstructed["aBox"]) assertEquals(visionManager.encodeToJsonElement(vision["aBox"]!!), visionManager.encodeToJsonElement(reconstructed["aBox"]!!))
} }
} }

View File

@ -3,6 +3,8 @@ package hep.dataforge.vision.solid
import hep.dataforge.vision.get import hep.dataforge.vision.get
import hep.dataforge.vision.style import hep.dataforge.vision.style
import hep.dataforge.vision.useStyle import hep.dataforge.vision.useStyle
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.encodeToJsonElement
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
@ -22,4 +24,11 @@ class SolidReferenceTest {
fun testReferenceProperty(){ fun testReferenceProperty(){
assertEquals("blue", (groupWithReference["test"] as Solid).color.string) assertEquals("blue", (groupWithReference["test"] as Solid).color.string)
} }
@Test
fun testReferenceSerialization(){
val serialized = SolidManager.jsonForSolids.encodeToJsonElement(groupWithReference)
val deserialized = SolidManager.jsonForSolids.decodeFromJsonElement(SolidGroup.serializer(), serialized)
assertEquals("blue", (deserialized["test"] as Solid).color.string)
}
} }

View File

@ -126,9 +126,8 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
} }
} }
override fun rateVision(vision: Vision): Int { override fun rateVision(vision: Vision): Int =
return if (vision is Solid) ElementVisionRenderer.DEFAULT_RATING else ElementVisionRenderer.ZERO_RATING if (vision is Solid) ElementVisionRenderer.DEFAULT_RATING else ElementVisionRenderer.ZERO_RATING
}
public fun renderSolid( public fun renderSolid(
element: Element, element: Element,
@ -141,7 +140,7 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
override fun render(element: Element, vision: Vision, meta: Meta) { override fun render(element: Element, vision: Vision, meta: Meta) {
renderSolid( renderSolid(
element, element,
vision as? Solid ?: error("Solid expected but ${vision::class} is found"), vision as? Solid ?: error("Solid expected but ${vision::class} found"),
Canvas3DOptions.read(meta) Canvas3DOptions.read(meta)
) )
} }
@ -157,7 +156,7 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
* Ensure that [ThreePlugin] is loaded in the global [VisionForge] context * Ensure that [ThreePlugin] is loaded in the global [VisionForge] context
*/ */
@JsExport @JsExport
public fun loadThreeJs() { public fun withThreeJs() {
VisionForge.plugins.fetch(ThreePlugin) VisionForge.plugins.fetch(ThreePlugin)
} }

View File

@ -1,9 +1,9 @@
package hep.dataforge.vision.three.server package hep.dataforge.vision.three.server
import hep.dataforge.vision.renderVisionsInWindow import hep.dataforge.vision.renderVisionsInWindow
import hep.dataforge.vision.solid.three.loadThreeJs import hep.dataforge.vision.solid.three.withThreeJs
public fun main() { public fun main() {
loadThreeJs() withThreeJs()
renderVisionsInWindow() renderVisionsInWindow()
} }