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.vision.VisionManager
import hep.dataforge.vision.gdml.gdml
import hep.dataforge.vision.html.Page
import hep.dataforge.vision.html.embedVisionFragment
import hep.dataforge.vision.html.fragment
import hep.dataforge.vision.html.scriptHeader
import hep.dataforge.vision.html.*
import hep.dataforge.vision.plotly.PlotlyPlugin
import hep.dataforge.vision.plotly.VisionOfPlotly
import hep.dataforge.vision.solid.SolidManager
import hep.dataforge.vision.solid.solid
import hep.dataforge.vision.visionManager
import kotlinx.html.div
import kotlinx.html.id
import kotlinx.html.script
import kotlinx.html.stream.createHTML
import kotlinx.html.unsafe
import kscience.plotly.Plot
import kscience.plotly.PlotlyFragment
import org.jetbrains.kotlinx.jupyter.api.HTML
import org.jetbrains.kotlinx.jupyter.api.Notebook
import org.jetbrains.kotlinx.jupyter.api.annotations.JupyterLibrary
import org.jetbrains.kotlinx.jupyter.api.libraries.*
import org.jetbrains.kotlinx.jupyter.api.libraries.ResourceLocation
import space.kscience.gdml.Gdml
@JupyterLibrary
@ -34,16 +35,25 @@ internal class VisionForgePlayGroundForJupyter : JupyterIntegration() {
ResourcePathType.CLASSPATH_PATH)))
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?) {
resource(jsResource)
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 ->
val fragment = VisionManager.fragment {
vision {
@ -53,11 +63,7 @@ internal class VisionForgePlayGroundForJupyter : JupyterIntegration() {
}
}
val html = createHTML().div {
embedVisionFragment(context.visionManager, fragment = fragment)
}
HTML(html)
HTML(produceHtmlVisionString(fragment))
}
render<Plot> { plot ->
@ -67,11 +73,7 @@ internal class VisionForgePlayGroundForJupyter : JupyterIntegration() {
}
}
val html = createHTML().div {
embedVisionFragment(context.visionManager, fragment = fragment)
}
HTML(html)
HTML( produceHtmlVisionString(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.renderVisionsAt
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() {
withPlotly()
loadThreeJs()
withThreeJs()
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.vision.VisionForge
import hep.dataforge.vision.VisionManager
import hep.dataforge.vision.html.ResourceLocation
import hep.dataforge.vision.html.fragment
import hep.dataforge.vision.plotly.plotly
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.withSolids
@OptIn(DFExperimental::class)
@DFExperimental
fun main() {
val content = VisionManager.fragment {
vision("canvas") {

View File

@ -83,7 +83,7 @@ public abstract class VisionTagConsumer<R>(
visionProvider: VisionOutput.() -> Vision,
): T = vision(name.toName(), visionProvider)
public inline fun <T> TagConsumer<T>.vision(
public fun <T> TagConsumer<T>.vision(
vision: 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.vision.client.VisionClient
import hep.dataforge.vision.client.renderAllVisions
import hep.dataforge.vision.client.renderAllVisionsAt
import kotlinx.browser.document
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(VisionClient)
}
/**
* Render all visions in this [window] using current global state of [VisionForge]
*/
@JsExport
public fun renderVisionsInWindow(){
public fun renderVisionsInWindow() {
window.onload = {
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 findRendererFor(vision: Vision): ElementVisionRenderer? =
getRenderers().maxByOrNull { it.rateVision(vision) }
private fun findRendererFor(vision: Vision): ElementVisionRenderer? {
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

View File

@ -8,6 +8,10 @@ import kotlinx.html.unsafe
import java.nio.file.Files
import java.nio.file.Path
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.
@ -37,21 +41,29 @@ public enum class ResourceLocation {
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
* @param
*/
@OptIn(ExperimentalPathApi::class)
internal fun checkOrStoreFile(htmlPath: Path, filePath: Path, resource: String): Path {
//TODO add logging
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 md = MessageDigest.getInstance("MD5")
val bytes = VisionManager::class.java.getResourceAsStream("/$resource").readAllBytes()
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.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)) {

View File

@ -1,38 +1,36 @@
package hep.dataforge.vision.gdml
import hep.dataforge.names.toName
import hep.dataforge.vision.solid.SolidManager
import space.kscience.gdml.Gdml
import nl.adaptivity.xmlutil.StAXReader
import org.junit.jupiter.api.Test
import space.kscience.gdml.decodeFromStream
import kotlin.test.assertNotNull
class TestConvertor {
@Test
fun testBMNGeometry() {
val stream = javaClass.getResourceAsStream("/gdml/BM@N.gdml")
val xmlReader = StAXReader(stream, "UTF-8")
val xml = Gdml.format.parse(Gdml.serializer(), xmlReader)
val vision = xml.toVision()
println(SolidManager.encodeToString(vision))
val gdml = Gdml.decodeFromStream(stream)
val vision = gdml.toVision()
//println(SolidManager.encodeToString(vision))
}
@Test
fun testCubes() {
val stream = javaClass.getResourceAsStream("/gdml/cubes.gdml")
val xmlReader = StAXReader(stream, "UTF-8")
val xml = Gdml.format.parse(Gdml.serializer(), xmlReader)
val visual = xml.toVision()
// println(visual)
val gdml = Gdml.decodeFromStream(stream)
val vision = gdml.toVision()
assertNotNull(vision.getPrototype("solids.box".toName()))
//println(SolidManager.encodeToString(vision))
}
@Test
fun testSimple() {
val stream = javaClass.getResourceAsStream("/gdml/simple1.gdml")
val xmlReader = StAXReader(stream, "UTF-8")
val xml = Gdml.format.parse(Gdml.serializer(), xmlReader)
val vision = xml.toVision()
println(SolidManager.encodeToString(vision))
val gdml = Gdml.decodeFromStream(stream)
val vision = gdml.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.meta.Meta
import hep.dataforge.names.Name
import hep.dataforge.names.asName
import hep.dataforge.vision.Vision
import hep.dataforge.vision.VisionForge
import hep.dataforge.vision.VisionPlugin
@ -26,11 +28,18 @@ public actual class PlotlyPlugin : VisionPlugin(), ElementVisionRenderer {
}
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)
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> {
override val tag: PluginTag = PluginTag("vision.plotly", PluginTag.DATAFORGE_GROUP)
override val type: KClass<PlotlyPlugin> = PlotlyPlugin::class

View File

@ -35,27 +35,6 @@ public class Box(
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 {
}

View File

@ -28,9 +28,13 @@ public interface PrototypeHolder {
@Serializable
@SerialName("group.solid")
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 {
init {
prototypes?.parent = this
}
override val descriptor: NodeDescriptor get() = Solid.descriptor
/**

View File

@ -8,21 +8,21 @@ import kotlin.test.assertEquals
class SolidPluginTest {
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
}
}
@DFExperimental
@Test
fun testPluginConverter(){
fun testPluginConverter() {
val visionManager = Global.plugins.fetch(SolidManager).visionManager
val meta = visionManager.encodeToMeta(vision)
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.style
import hep.dataforge.vision.useStyle
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.encodeToJsonElement
import kotlin.test.Test
import kotlin.test.assertEquals
@ -22,4 +24,11 @@ class SolidReferenceTest {
fun testReferenceProperty(){
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 {
return if (vision is Solid) ElementVisionRenderer.DEFAULT_RATING else ElementVisionRenderer.ZERO_RATING
}
override fun rateVision(vision: Vision): Int =
if (vision is Solid) ElementVisionRenderer.DEFAULT_RATING else ElementVisionRenderer.ZERO_RATING
public fun renderSolid(
element: Element,
@ -141,7 +140,7 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
override fun render(element: Element, vision: Vision, meta: Meta) {
renderSolid(
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)
)
}
@ -157,7 +156,7 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
* Ensure that [ThreePlugin] is loaded in the global [VisionForge] context
*/
@JsExport
public fun loadThreeJs() {
public fun withThreeJs() {
VisionForge.plugins.fetch(ThreePlugin)
}

View File

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