Fix a lot of bugs
This commit is contained in:
parent
6a48948c15
commit
cae3ab00d9
@ -1,6 +1,7 @@
|
|||||||
package hep.dataforge.vision.gdml.demo
|
package hep.dataforge.vision.gdml.demo
|
||||||
|
|
||||||
import hep.dataforge.context.Context
|
import hep.dataforge.context.Context
|
||||||
|
import hep.dataforge.meta.invoke
|
||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.Name
|
||||||
import hep.dataforge.vision.Vision
|
import hep.dataforge.vision.Vision
|
||||||
import hep.dataforge.vision.bootstrap.card
|
import hep.dataforge.vision.bootstrap.card
|
||||||
@ -12,6 +13,7 @@ import hep.dataforge.vision.react.flexColumn
|
|||||||
import hep.dataforge.vision.react.flexRow
|
import hep.dataforge.vision.react.flexRow
|
||||||
import hep.dataforge.vision.solid.Solid
|
import hep.dataforge.vision.solid.Solid
|
||||||
import hep.dataforge.vision.solid.SolidManager
|
import hep.dataforge.vision.solid.SolidManager
|
||||||
|
import hep.dataforge.vision.solid.specifications.Canvas3DOptions
|
||||||
import hep.dataforge.vision.solid.three.ThreeCanvas
|
import hep.dataforge.vision.solid.three.ThreeCanvas
|
||||||
import kotlinx.browser.window
|
import kotlinx.browser.window
|
||||||
import kotlinx.css.*
|
import kotlinx.css.*
|
||||||
@ -104,7 +106,9 @@ val GDMLApp = functionalComponent<GDMLAppProps>("GDMLApp") { props ->
|
|||||||
this.context = props.context
|
this.context = props.context
|
||||||
this.obj = vision as? Solid
|
this.obj = vision as? Solid
|
||||||
this.selected = selected
|
this.selected = selected
|
||||||
this.clickCallback = onSelect
|
this.options = Canvas3DOptions.invoke{
|
||||||
|
this.onSelect = onSelect
|
||||||
|
}
|
||||||
this.canvasCallback = {
|
this.canvasCallback = {
|
||||||
canvas = it
|
canvas = it
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
package hep.dataforge.vision.gdml.demo
|
package hep.dataforge.vision.gdml.demo
|
||||||
|
|
||||||
import hep.dataforge.context.Global
|
import hep.dataforge.context.Global
|
||||||
|
import hep.dataforge.vision.VisionManager
|
||||||
import hep.dataforge.vision.editor.VisualObjectEditorFragment
|
import hep.dataforge.vision.editor.VisualObjectEditorFragment
|
||||||
import hep.dataforge.vision.editor.VisualObjectTreeFragment
|
import hep.dataforge.vision.editor.VisualObjectTreeFragment
|
||||||
import hep.dataforge.vision.gdml.toVision
|
import hep.dataforge.vision.gdml.toVision
|
||||||
import hep.dataforge.vision.VisionManager
|
|
||||||
import hep.dataforge.vision.solid.Solid
|
import hep.dataforge.vision.solid.Solid
|
||||||
import hep.dataforge.vision.solid.SolidManager
|
|
||||||
import hep.dataforge.vision.solid.SolidMaterial
|
import hep.dataforge.vision.solid.SolidMaterial
|
||||||
import hep.dataforge.vision.solid.fx.FX3DPlugin
|
import hep.dataforge.vision.solid.fx.FX3DPlugin
|
||||||
import hep.dataforge.vision.solid.fx.FXCanvas3D
|
import hep.dataforge.vision.solid.fx.FXCanvas3D
|
||||||
@ -27,7 +26,7 @@ class GDMLView : View() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val propertyEditor = VisualObjectEditorFragment {
|
private val propertyEditor = VisualObjectEditorFragment {
|
||||||
it.getAllProperties()
|
it.allProperties
|
||||||
}.apply {
|
}.apply {
|
||||||
descriptorProperty.set(SolidMaterial.descriptor)
|
descriptorProperty.set(SolidMaterial.descriptor)
|
||||||
itemProperty.bind(treeFragment.selectedProperty)
|
itemProperty.bind(treeFragment.selectedProperty)
|
||||||
|
@ -51,7 +51,7 @@ val MMApp = functionalComponent<MMAppProps>("Muon monitor") { props ->
|
|||||||
var selected by useState { props.selected }
|
var selected by useState { props.selected }
|
||||||
var canvas: ThreeCanvas? by useState { null }
|
var canvas: ThreeCanvas? by useState { null }
|
||||||
|
|
||||||
val select: (Name?) -> Unit = {
|
val onSelect: (Name?) -> Unit = {
|
||||||
selected = it
|
selected = it
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,7 +72,7 @@ val MMApp = functionalComponent<MMAppProps>("Muon monitor") { props ->
|
|||||||
}
|
}
|
||||||
//tree
|
//tree
|
||||||
card("Object tree") {
|
card("Object tree") {
|
||||||
objectTree(root, selected, select)
|
objectTree(root, selected, onSelect)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
styledDiv {
|
styledDiv {
|
||||||
@ -85,9 +85,10 @@ val MMApp = functionalComponent<MMAppProps>("Muon monitor") { props ->
|
|||||||
attrs {
|
attrs {
|
||||||
this.context = props.context
|
this.context = props.context
|
||||||
this.obj = root
|
this.obj = root
|
||||||
this.options = canvasConfig
|
this.options = canvasConfig.apply {
|
||||||
|
this.onSelect = onSelect
|
||||||
|
}
|
||||||
this.selected = selected
|
this.selected = selected
|
||||||
this.clickCallback = select
|
|
||||||
this.canvasCallback = {
|
this.canvasCallback = {
|
||||||
canvas = it
|
canvas = it
|
||||||
}
|
}
|
||||||
@ -176,7 +177,7 @@ val MMApp = functionalComponent<MMAppProps>("Muon monitor") { props ->
|
|||||||
configEditor(
|
configEditor(
|
||||||
selectedObject.config,
|
selectedObject.config,
|
||||||
selectedObject.descriptor,
|
selectedObject.descriptor,
|
||||||
default = selectedObject.getAllProperties(),
|
default = selectedObject.allProperties,
|
||||||
key = selected
|
key = selected
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import ru.mipt.npm.gradle.*
|
import ru.mipt.npm.gradle.DependencyConfiguration
|
||||||
|
import ru.mipt.npm.gradle.FXModule
|
||||||
|
import ru.mipt.npm.gradle.useFx
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("ru.mipt.npm.mpp")
|
id("ru.mipt.npm.mpp")
|
||||||
@ -23,8 +25,13 @@ kotlin {
|
|||||||
sourceSets {
|
sourceSets {
|
||||||
commonMain {
|
commonMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
api(project(":visionforge-solid"))
|
implementation(project(":visionforge-solid"))
|
||||||
api(project(":visionforge-gdml"))
|
implementation(project(":visionforge-gdml"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jsMain{
|
||||||
|
dependencies {
|
||||||
|
implementation("org.jetbrains:kotlin-css:1.0.0-pre.129-kotlin-1.4.10")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,8 @@ package hep.dataforge.vision.solid.demo
|
|||||||
import hep.dataforge.meta.Meta
|
import hep.dataforge.meta.Meta
|
||||||
import hep.dataforge.meta.invoke
|
import hep.dataforge.meta.invoke
|
||||||
import hep.dataforge.names.toName
|
import hep.dataforge.names.toName
|
||||||
import hep.dataforge.output.OutputManager
|
|
||||||
import hep.dataforge.vision.Colors
|
import hep.dataforge.vision.Colors
|
||||||
import hep.dataforge.vision.Vision
|
import hep.dataforge.vision.layout.Page
|
||||||
import hep.dataforge.vision.solid.*
|
import hep.dataforge.vision.solid.*
|
||||||
import hep.dataforge.vision.solid.specifications.Canvas3DOptions
|
import hep.dataforge.vision.solid.specifications.Canvas3DOptions
|
||||||
import hep.dataforge.vision.visible
|
import hep.dataforge.vision.visible
|
||||||
@ -16,12 +15,12 @@ import kotlin.math.sin
|
|||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
|
||||||
fun OutputManager.demo(name: String, title: String = name, block: SolidGroup.() -> Unit) {
|
fun Page<Solid>.demo(name: String, title: String = name, block: SolidGroup.() -> Unit) {
|
||||||
val meta = Meta {
|
val meta = Meta {
|
||||||
"title" put title
|
"title" put title
|
||||||
}
|
}
|
||||||
val output = get(Vision::class, name.toName(), meta = meta)
|
val output = output(name.toName(), meta)?: error("Output with name $name not found")
|
||||||
output.render (action = block)
|
output.solidGroup (builder = block)
|
||||||
}
|
}
|
||||||
|
|
||||||
val canvasOptions = Canvas3DOptions {
|
val canvasOptions = Canvas3DOptions {
|
||||||
@ -36,7 +35,7 @@ val canvasOptions = Canvas3DOptions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun OutputManager.showcase() {
|
fun Page<Solid>.showcase() {
|
||||||
demo("shapes", "Basic shapes") {
|
demo("shapes", "Basic shapes") {
|
||||||
box(100.0, 100.0, 100.0) {
|
box(100.0, 100.0, 100.0) {
|
||||||
z = -110.0
|
z = -110.0
|
||||||
@ -133,7 +132,7 @@ fun OutputManager.showcase() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun OutputManager.showcaseCSG() {
|
fun Page<Solid>.showcaseCSG() {
|
||||||
demo("CSG.simple", "CSG operations") {
|
demo("CSG.simple", "CSG operations") {
|
||||||
composite(CompositeType.UNION) {
|
composite(CompositeType.UNION) {
|
||||||
box(100, 100, 100) {
|
box(100, 100, 100) {
|
||||||
|
@ -15,7 +15,7 @@ private class ThreeDemoApp : Application {
|
|||||||
|
|
||||||
override fun start(state: Map<String, Any>) {
|
override fun start(state: Map<String, Any>) {
|
||||||
|
|
||||||
val element = document.getElementById("canvas") ?: error("Element with id 'canvas' not found on page")
|
val element = document.getElementById("demo") ?: error("Element with id 'demo' not found on page")
|
||||||
|
|
||||||
ThreeDemoGrid(element).run {
|
ThreeDemoGrid(element).run {
|
||||||
showcase()
|
showcase()
|
||||||
|
@ -5,57 +5,75 @@ import hep.dataforge.meta.Meta
|
|||||||
import hep.dataforge.meta.get
|
import hep.dataforge.meta.get
|
||||||
import hep.dataforge.meta.string
|
import hep.dataforge.meta.string
|
||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.Name
|
||||||
import hep.dataforge.output.Renderer
|
import hep.dataforge.vision.layout.Output
|
||||||
import hep.dataforge.vision.Vision
|
import hep.dataforge.vision.layout.Page
|
||||||
|
import hep.dataforge.vision.solid.Solid
|
||||||
import hep.dataforge.vision.solid.three.ThreeCanvas
|
import hep.dataforge.vision.solid.three.ThreeCanvas
|
||||||
import hep.dataforge.vision.solid.three.ThreePlugin
|
import hep.dataforge.vision.solid.three.ThreePlugin
|
||||||
import hep.dataforge.vision.solid.three.attachRenderer
|
|
||||||
import kotlinx.browser.document
|
|
||||||
import kotlinx.dom.clear
|
import kotlinx.dom.clear
|
||||||
import kotlinx.html.dom.append
|
import kotlinx.html.dom.append
|
||||||
import kotlinx.html.dom.create
|
|
||||||
import kotlinx.html.h2
|
|
||||||
import kotlinx.html.hr
|
|
||||||
import kotlinx.html.id
|
import kotlinx.html.id
|
||||||
import kotlinx.html.js.div
|
import kotlinx.html.js.*
|
||||||
import kotlinx.html.span
|
import kotlinx.html.role
|
||||||
import org.w3c.dom.Element
|
import org.w3c.dom.Element
|
||||||
import kotlin.reflect.KClass
|
import org.w3c.dom.HTMLDivElement
|
||||||
|
import org.w3c.dom.HTMLElement
|
||||||
|
|
||||||
class ThreeDemoGrid(element: Element, meta: Meta = Meta.EMPTY) {
|
class ThreeDemoGrid(element: Element, idPrefix: String = "") : Page<Solid> {
|
||||||
|
|
||||||
|
|
||||||
|
private lateinit var navigationElement: HTMLElement
|
||||||
|
private lateinit var contentElement: HTMLDivElement
|
||||||
|
|
||||||
private val gridRoot = document.create.div("row")
|
|
||||||
private val outputs: MutableMap<Name, ThreeCanvas> = HashMap()
|
private val outputs: MutableMap<Name, ThreeCanvas> = HashMap()
|
||||||
|
|
||||||
private val three = Global.plugins.fetch(ThreePlugin)
|
private val three = Global.plugins.fetch(ThreePlugin)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
element.clear()
|
element.clear()
|
||||||
element.append(gridRoot)
|
element.append {
|
||||||
|
div("container") {
|
||||||
|
navigationElement = ul("nav nav-tabs") {
|
||||||
|
id = "${idPrefix}Tab"
|
||||||
|
role = "tablist"
|
||||||
|
}
|
||||||
|
contentElement = div("tab-content") {
|
||||||
|
id = "${idPrefix}TabContent"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
override fun <T : Any> get(type: KClass<out T>, name: Name, stage: Name, meta: Meta): Renderer<T> {
|
|
||||||
|
|
||||||
return outputs.getOrPut(name) {
|
@Suppress("UNCHECKED_CAST")
|
||||||
if (type != Vision::class) error("Supports only DisplayObject")
|
override fun output(name: Name, meta: Meta): Output<Solid> = outputs.getOrPut(name) {
|
||||||
lateinit var output: ThreeCanvas
|
lateinit var output: ThreeCanvas
|
||||||
//TODO calculate cell width here using jquery
|
navigationElement.append {
|
||||||
gridRoot.append {
|
li("nav-item") {
|
||||||
span("border") {
|
a(classes = "nav-link") {
|
||||||
div("col-6") {
|
id = "tab[$name]"
|
||||||
div { id = "output-$name" }.also {
|
attributes["data-toggle"] = "tab"
|
||||||
output = three.attachRenderer(it, canvasOptions)
|
href = "#$name"
|
||||||
//output.attach(it)
|
role = "tab"
|
||||||
|
attributes["aria-controls"] = "$name"
|
||||||
|
attributes["aria-selected"] = "false"
|
||||||
|
+name.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
contentElement.append {
|
||||||
|
div("tab-pane fade col h-100") {
|
||||||
|
id = name.toString()
|
||||||
|
role = "tabpanel"
|
||||||
|
attributes["aria-labelledby"] = "tab[$name]"
|
||||||
|
div("container w-100 h-100") { id = "output-$name" }.also {element->
|
||||||
|
output = three.createCanvas(element, canvasOptions)
|
||||||
}
|
}
|
||||||
hr()
|
hr()
|
||||||
h2 { +(meta["title"].string ?: name.toString()) }
|
h2 { +(meta["title"].string ?: name.toString()) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
output
|
output
|
||||||
} as Renderer<T>
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ import hep.dataforge.vision.set
|
|||||||
import hep.dataforge.vision.setProperty
|
import hep.dataforge.vision.setProperty
|
||||||
import hep.dataforge.vision.solid.*
|
import hep.dataforge.vision.solid.*
|
||||||
import hep.dataforge.vision.solid.Solid.Companion.GEOMETRY_KEY
|
import hep.dataforge.vision.solid.Solid.Companion.GEOMETRY_KEY
|
||||||
|
import hep.dataforge.vision.solid.SolidMaterial.Companion.MATERIAL_COLOR_KEY
|
||||||
import hep.dataforge.vision.solid.three.*
|
import hep.dataforge.vision.solid.three.*
|
||||||
import hep.dataforge.vision.solid.three.ThreeMaterials.getMaterial
|
import hep.dataforge.vision.solid.three.ThreeMaterials.getMaterial
|
||||||
import info.laht.threekt.core.BufferGeometry
|
import info.laht.threekt.core.BufferGeometry
|
||||||
@ -75,6 +76,9 @@ internal class VariableBox(xSize: Number, ySize: Number, zSize: Number) : ThreeV
|
|||||||
}
|
}
|
||||||
name.startsWith(MeshThreeFactory.WIREFRAME_KEY) -> mesh.applyWireFrame(this@VariableBox)
|
name.startsWith(MeshThreeFactory.WIREFRAME_KEY) -> mesh.applyWireFrame(this@VariableBox)
|
||||||
name.startsWith(MeshThreeFactory.EDGES_KEY) -> mesh.applyEdges(this@VariableBox)
|
name.startsWith(MeshThreeFactory.EDGES_KEY) -> mesh.applyEdges(this@VariableBox)
|
||||||
|
name.startsWith(MATERIAL_COLOR_KEY)->{
|
||||||
|
mesh.material = getMaterial(this, true)
|
||||||
|
}
|
||||||
else -> mesh.updateProperty(this@VariableBox, name)
|
else -> mesh.updateProperty(this@VariableBox, name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,14 +4,25 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
<title>Three js demo for particle physics</title>
|
<title>Three js demo for particle physics</title>
|
||||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
|
<!-- CSS -->
|
||||||
integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css"
|
||||||
|
integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
|
||||||
|
|
||||||
<script type="text/javascript" src="spatial-showcase.js"></script>
|
<script type="text/javascript" src="spatial-showcase.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body class="application">
|
<body class="application">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>Demo grid</h1>
|
<h1>Demo grid</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="container" id="canvas"></div>
|
<div class="container" id="demo"></div>
|
||||||
|
|
||||||
|
<!-- jQuery and JS bundle w/ Popper.js -->
|
||||||
|
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
|
||||||
|
integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js"
|
||||||
|
integrity="sha384-ho+j7jyWK8fNQe+A12Hb8AhRq26LrZ/JpcUGGOn+Y7RsweNrtN/tE3MoK7ZeZDyx"
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
@ -3,18 +3,17 @@ package hep.dataforge.vision.solid.demo
|
|||||||
import hep.dataforge.context.Global
|
import hep.dataforge.context.Global
|
||||||
import hep.dataforge.meta.Meta
|
import hep.dataforge.meta.Meta
|
||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.Name
|
||||||
import hep.dataforge.output.OutputManager
|
import hep.dataforge.vision.layout.Output
|
||||||
import hep.dataforge.output.Renderer
|
import hep.dataforge.vision.layout.Page
|
||||||
import hep.dataforge.vision.Vision
|
import hep.dataforge.vision.solid.Solid
|
||||||
import hep.dataforge.vision.solid.fx.FX3DPlugin
|
import hep.dataforge.vision.solid.fx.FX3DPlugin
|
||||||
import hep.dataforge.vision.solid.fx.FXCanvas3D
|
import hep.dataforge.vision.solid.fx.FXCanvas3D
|
||||||
import javafx.collections.FXCollections
|
import javafx.collections.FXCollections
|
||||||
import javafx.scene.Parent
|
import javafx.scene.Parent
|
||||||
import javafx.scene.control.Tab
|
import javafx.scene.control.Tab
|
||||||
import tornadofx.*
|
import tornadofx.*
|
||||||
import kotlin.reflect.KClass
|
|
||||||
|
|
||||||
class FXDemoGrid : View(title = "DataForge-vis FX demo"), OutputManager {
|
class FXDemoGrid : View(title = "DataForge-vis FX demo"), Page<Solid> {
|
||||||
private val outputs = FXCollections.observableHashMap<Name, FXCanvas3D>()
|
private val outputs = FXCollections.observableHashMap<Name, FXCanvas3D>()
|
||||||
|
|
||||||
override val root: Parent = borderpane {
|
override val root: Parent = borderpane {
|
||||||
@ -27,14 +26,8 @@ class FXDemoGrid : View(title = "DataForge-vis FX demo"), OutputManager {
|
|||||||
|
|
||||||
private val fx3d = Global.plugins.fetch(FX3DPlugin)
|
private val fx3d = Global.plugins.fetch(FX3DPlugin)
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
override fun output(name: Name, meta: Meta): Output<Solid> = outputs.getOrPut(name) {
|
||||||
override fun <T : Any> get(type: KClass<out T>, name: Name, stage: Name, meta: Meta): Renderer<T> {
|
FXCanvas3D(fx3d, canvasOptions)
|
||||||
return outputs.getOrPut(name) {
|
|
||||||
if (type != Vision::class) kotlin.error("Supports only DisplayObject")
|
|
||||||
val output = FXCanvas3D(fx3d, canvasOptions)
|
|
||||||
|
|
||||||
output
|
|
||||||
} as Renderer<T>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -54,7 +54,7 @@ public val ThreeControls: FunctionalComponent<ThreeControlsProps> = functionalCo
|
|||||||
if (selectedObject != null) {
|
if (selectedObject != null) {
|
||||||
visionPropertyEditor(
|
visionPropertyEditor(
|
||||||
selectedObject,
|
selectedObject,
|
||||||
default = selectedObject.getAllProperties(),
|
default = selectedObject.allProperties,
|
||||||
key = selected
|
key = selected
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,6 @@ public external interface ThreeCanvasProps : RProps {
|
|||||||
public var obj: Solid?
|
public var obj: Solid?
|
||||||
public var options: Canvas3DOptions?
|
public var options: Canvas3DOptions?
|
||||||
public var selected: Name?
|
public var selected: Name?
|
||||||
public var clickCallback: (Name?) -> Unit
|
|
||||||
public var canvasCallback: ((ThreeCanvas?) -> Unit)?
|
public var canvasCallback: ((ThreeCanvas?) -> Unit)?
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,7 +38,7 @@ public val ThreeCanvasComponent: FunctionalComponent<ThreeCanvasProps> = functio
|
|||||||
val element = elementRef.current as? HTMLElement ?: error("Canvas element not found")
|
val element = elementRef.current as? HTMLElement ?: error("Canvas element not found")
|
||||||
val three: ThreePlugin = props.context.plugins.fetch(ThreePlugin)
|
val three: ThreePlugin = props.context.plugins.fetch(ThreePlugin)
|
||||||
val newCanvas: ThreeCanvas =
|
val newCanvas: ThreeCanvas =
|
||||||
three.attachRenderer(element, props.options ?: Canvas3DOptions.empty(), props.clickCallback)
|
three.createCanvas(element, props.options ?: Canvas3DOptions.empty())
|
||||||
props.canvasCallback?.invoke(newCanvas)
|
props.canvasCallback?.invoke(newCanvas)
|
||||||
canvas = newCanvas
|
canvas = newCanvas
|
||||||
}
|
}
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
package hep.dataforge.vision
|
|
||||||
|
|
||||||
public fun interface Renderer<in V: Vision> {
|
|
||||||
public fun render(vision: V)
|
|
||||||
}
|
|
@ -12,8 +12,6 @@ import hep.dataforge.vision.Vision.Companion.TYPE
|
|||||||
import hep.dataforge.vision.Vision.Companion.VISIBLE_KEY
|
import hep.dataforge.vision.Vision.Companion.VISIBLE_KEY
|
||||||
import kotlinx.serialization.PolymorphicSerializer
|
import kotlinx.serialization.PolymorphicSerializer
|
||||||
import kotlinx.serialization.Transient
|
import kotlinx.serialization.Transient
|
||||||
import kotlin.properties.ReadWriteProperty
|
|
||||||
import kotlin.reflect.KProperty
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A root type for display hierarchy
|
* A root type for display hierarchy
|
||||||
@ -35,7 +33,7 @@ public interface Vision : Configurable, Described {
|
|||||||
/**
|
/**
|
||||||
* All properties including styles and prototypes if present, including inherited ones
|
* All properties including styles and prototypes if present, including inherited ones
|
||||||
*/
|
*/
|
||||||
public fun getAllProperties(): Laminate
|
public val allProperties: Laminate
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get property (including styles). [inherit] toggles parent node property lookup
|
* Get property (including styles). [inherit] toggles parent node property lookup
|
||||||
@ -116,27 +114,27 @@ public var Vision.visible: Boolean?
|
|||||||
get() = getProperty(VISIBLE_KEY).boolean
|
get() = getProperty(VISIBLE_KEY).boolean
|
||||||
set(value) = config.setValue(VISIBLE_KEY, value?.asValue())
|
set(value) = config.setValue(VISIBLE_KEY, value?.asValue())
|
||||||
|
|
||||||
/**
|
///**
|
||||||
* Convinience delegate for properties
|
// * Convenience delegate for properties
|
||||||
*/
|
// */
|
||||||
public fun Vision.property(
|
//public fun Vision.property(
|
||||||
default: MetaItem<*>? = null,
|
// default: MetaItem<*>? = null,
|
||||||
key: Name? = null,
|
// key: Name? = null,
|
||||||
inherit: Boolean = true,
|
// inherit: Boolean = true,
|
||||||
): MutableItemDelegate =
|
//): MutableItemDelegate =
|
||||||
object : ReadWriteProperty<Any?, MetaItem<*>?> {
|
// object : ReadWriteProperty<Any?, MetaItem<*>?> {
|
||||||
override fun getValue(thisRef: Any?, property: KProperty<*>): MetaItem<*>? {
|
// override fun getValue(thisRef: Any?, property: KProperty<*>): MetaItem<*>? {
|
||||||
val name = key ?: property.name.toName()
|
// val name = key ?: property.name.toName()
|
||||||
return getProperty(name, inherit) ?: default
|
// return getProperty(name, inherit) ?: default
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
|
// override fun setValue(thisRef: Any?, property: KProperty<*>, value: MetaItem<*>?) {
|
||||||
|
// val name = key ?: property.name.toName()
|
||||||
|
// setProperty(name, value)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
override fun setValue(thisRef: Any?, property: KProperty<*>, value: MetaItem<*>?) {
|
public fun Vision.props(inherit: Boolean = true): MutableItemProvider = object : MutableItemProvider {
|
||||||
val name = key ?: property.name.toName()
|
|
||||||
setProperty(name, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun Vision.properties(inherit: Boolean = true): MutableItemProvider = object : MutableItemProvider {
|
|
||||||
override fun getItem(name: Name): MetaItem<*>? {
|
override fun getItem(name: Name): MetaItem<*>? {
|
||||||
return getProperty(name, inherit)
|
return getProperty(name, inherit)
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package hep.dataforge.vision
|
|||||||
import hep.dataforge.meta.*
|
import hep.dataforge.meta.*
|
||||||
import hep.dataforge.meta.descriptors.NodeDescriptor
|
import hep.dataforge.meta.descriptors.NodeDescriptor
|
||||||
import hep.dataforge.meta.descriptors.defaultItem
|
import hep.dataforge.meta.descriptors.defaultItem
|
||||||
|
import hep.dataforge.meta.descriptors.defaultMeta
|
||||||
import hep.dataforge.meta.descriptors.get
|
import hep.dataforge.meta.descriptors.get
|
||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.Name
|
||||||
import hep.dataforge.names.asName
|
import hep.dataforge.names.asName
|
||||||
@ -75,24 +76,22 @@ public open class VisionBase : Vision {
|
|||||||
/**
|
/**
|
||||||
* All available properties in a layered form
|
* All available properties in a layered form
|
||||||
*/
|
*/
|
||||||
override fun getAllProperties(): Laminate = Laminate(properties, allStyles, parent?.getAllProperties())
|
override val allProperties: Laminate
|
||||||
|
get() = Laminate(
|
||||||
|
properties,
|
||||||
|
allStyles,
|
||||||
|
parent?.allProperties,
|
||||||
|
descriptor?.defaultMeta(),
|
||||||
|
)
|
||||||
|
|
||||||
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? {
|
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? = sequence {
|
||||||
return if (inherit) {
|
|
||||||
sequence {
|
|
||||||
yield(properties?.get(name))
|
yield(properties?.get(name))
|
||||||
yieldAll(getStyleItems(name))
|
yieldAll(getStyleItems(name))
|
||||||
yield(descriptor?.get(name)?.defaultItem())
|
if (inherit) {
|
||||||
yield(parent?.getProperty(name, inherit))
|
yield(parent?.getProperty(name, inherit))
|
||||||
}.merge()
|
}
|
||||||
} else {
|
|
||||||
sequence {
|
|
||||||
yield(properties?.get(name))
|
|
||||||
yieldAll(getStyleItems(name))
|
|
||||||
yield(descriptor?.get(name)?.defaultItem())
|
yield(descriptor?.get(name)?.defaultItem())
|
||||||
}.merge()
|
}.merge()
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset all properties to their default values
|
* Reset all properties to their default values
|
||||||
|
@ -19,17 +19,13 @@ public class VisionManager(meta: Meta) : AbstractPlugin(meta) {
|
|||||||
public val serializersModule: SerializersModule
|
public val serializersModule: SerializersModule
|
||||||
get() = SerializersModule {
|
get() = SerializersModule {
|
||||||
include(defaultSerialModule)
|
include(defaultSerialModule)
|
||||||
context.gather<SerializersModule>(VISION_SERIAL_MODULE_TARGET).values.forEach {
|
context.gather<SerializersModule>(VISION_SERIALIZER_MODULE_TARGET).values.forEach {
|
||||||
include(it)
|
include(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public val jsonFormat: Json
|
public val jsonFormat: Json
|
||||||
get() = Json {
|
get() = Json(defaultJson) {
|
||||||
prettyPrint = true
|
|
||||||
useArrayPolymorphism = false
|
|
||||||
encodeDefaults = false
|
|
||||||
ignoreUnknownKeys = true
|
|
||||||
serializersModule = this@VisionManager.serializersModule
|
serializersModule = this@VisionManager.serializersModule
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,14 +48,23 @@ public class VisionManager(meta: Meta) : AbstractPlugin(meta) {
|
|||||||
override val tag: PluginTag = PluginTag(name = "vision", group = PluginTag.DATAFORGE_GROUP)
|
override val tag: PluginTag = PluginTag(name = "vision", group = PluginTag.DATAFORGE_GROUP)
|
||||||
override val type: KClass<out VisionManager> = VisionManager::class
|
override val type: KClass<out VisionManager> = VisionManager::class
|
||||||
|
|
||||||
public const val VISION_SERIAL_MODULE_TARGET: String = "visionSerialModule"
|
public const val VISION_SERIALIZER_MODULE_TARGET: String = "visionSerializerModule"
|
||||||
|
|
||||||
override fun invoke(meta: Meta, context: Context): VisionManager = VisionManager(meta)
|
override fun invoke(meta: Meta, context: Context): VisionManager = VisionManager(meta)
|
||||||
|
|
||||||
private val defaultSerialModule: SerializersModule = SerializersModule {
|
private val defaultSerialModule: SerializersModule = SerializersModule {
|
||||||
polymorphic(Vision::class) {
|
polymorphic(Vision::class) {
|
||||||
|
subclass(VisionBase.serializer())
|
||||||
subclass(VisionGroupBase.serializer())
|
subclass(VisionGroupBase.serializer())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public val defaultJson: Json = Json {
|
||||||
|
serializersModule = defaultSerialModule
|
||||||
|
prettyPrint = true
|
||||||
|
useArrayPolymorphism = false
|
||||||
|
encodeDefaults = false
|
||||||
|
ignoreUnknownKeys = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package hep.dataforge.vision.html
|
||||||
|
|
||||||
|
import hep.dataforge.names.Name
|
||||||
|
import hep.dataforge.vision.Vision
|
||||||
|
import kotlinx.html.TagConsumer
|
||||||
|
|
||||||
|
public class BindingHtmlOutputScope<T, V : Vision>(
|
||||||
|
root: TagConsumer<T>,
|
||||||
|
prefix: String? = null,
|
||||||
|
) : HtmlOutputScope<T, V>(root,prefix) {
|
||||||
|
|
||||||
|
private val _bindings = HashMap<Name, V>()
|
||||||
|
public val bindings: Map<Name, V> get() = _bindings
|
||||||
|
|
||||||
|
override fun renderVision(htmlOutput: HtmlOutput<V>, vision: V) {
|
||||||
|
_bindings[htmlOutput.name] = vision
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
package hep.dataforge.vision.html
|
||||||
|
|
||||||
|
import hep.dataforge.names.Name
|
||||||
|
import hep.dataforge.names.toName
|
||||||
|
import hep.dataforge.vision.Vision
|
||||||
|
import kotlinx.html.DIV
|
||||||
|
import kotlinx.html.TagConsumer
|
||||||
|
import kotlinx.html.div
|
||||||
|
import kotlinx.html.id
|
||||||
|
|
||||||
|
public class HtmlOutput<V : Vision>(
|
||||||
|
public val outputScope: HtmlOutputScope<*, V>,
|
||||||
|
public val name: Name,
|
||||||
|
public val div: DIV,
|
||||||
|
)
|
||||||
|
|
||||||
|
public abstract class HtmlOutputScope<R, V : Vision>(
|
||||||
|
private val root: TagConsumer<R>,
|
||||||
|
public val prefix: String? = null,
|
||||||
|
) : TagConsumer<R> by root {
|
||||||
|
|
||||||
|
public open fun resolveId(name: Name): String = (prefix ?: "output:") + name.toString()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a placeholder but do not attach any [Vision] to it
|
||||||
|
*/
|
||||||
|
public inline fun <T> TagConsumer<T>.visionOutput(
|
||||||
|
name: Name,
|
||||||
|
crossinline block: HtmlOutput<V>.() -> Unit = {},
|
||||||
|
): T = div {
|
||||||
|
this.id = resolveId(name)
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
HtmlOutput(this@HtmlOutputScope, name, this).block()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public inline fun <T> TagConsumer<T>.visionOutput(
|
||||||
|
name: String,
|
||||||
|
crossinline block: HtmlOutput<V>.() -> Unit = {},
|
||||||
|
): T = visionOutput(name.toName(), block)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a placeholder and put a [Vision] in it
|
||||||
|
*/
|
||||||
|
public abstract fun renderVision(htmlOutput: HtmlOutput<V>, vision: V)
|
||||||
|
|
||||||
|
public fun <T> TagConsumer<T>.vision(name: Name, vision: V): Unit {
|
||||||
|
visionOutput(name) {
|
||||||
|
renderVision(this, vision)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package hep.dataforge.vision.html
|
||||||
|
|
||||||
|
import hep.dataforge.vision.Vision
|
||||||
|
|
||||||
|
public class HtmlVisionFragment<V : Vision>(public val layout: HtmlOutputScope<out Any, V>.() -> Unit)
|
||||||
|
|
||||||
|
public fun buildVisionFragment(visit: HtmlOutputScope<out Any, Vision>.() -> Unit): HtmlVisionFragment<Vision> =
|
||||||
|
HtmlVisionFragment(visit)
|
@ -0,0 +1,28 @@
|
|||||||
|
package hep.dataforge.vision.html
|
||||||
|
|
||||||
|
import hep.dataforge.vision.Vision
|
||||||
|
import kotlinx.html.FlowContent
|
||||||
|
import kotlinx.html.TagConsumer
|
||||||
|
import kotlinx.html.stream.createHTML
|
||||||
|
|
||||||
|
public typealias HtmlVisionRenderer<V> = FlowContent.(V) -> Unit
|
||||||
|
|
||||||
|
public class StaticHtmlOutputScope<R, V : Vision>(
|
||||||
|
root: TagConsumer<R>,
|
||||||
|
prefix: String? = null,
|
||||||
|
private val render: HtmlVisionRenderer<V>,
|
||||||
|
) : HtmlOutputScope<R, V>(root, prefix) {
|
||||||
|
|
||||||
|
override fun renderVision(htmlOutput: HtmlOutput<V>, vision: V) {
|
||||||
|
htmlOutput.div.render(vision)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun <T : Any> HtmlVisionFragment<Vision>.renderToObject(
|
||||||
|
root: TagConsumer<T>,
|
||||||
|
prefix: String? = null,
|
||||||
|
renderer: HtmlVisionRenderer<Vision>,
|
||||||
|
): T = StaticHtmlOutputScope(root, prefix, renderer).apply(layout).finalize()
|
||||||
|
|
||||||
|
public fun HtmlVisionFragment<Vision>.renderToString(renderer: HtmlVisionRenderer<Vision>): String =
|
||||||
|
renderToObject(createHTML(), null, renderer)
|
@ -0,0 +1,16 @@
|
|||||||
|
package hep.dataforge.vision.layout
|
||||||
|
|
||||||
|
import hep.dataforge.meta.Meta
|
||||||
|
import hep.dataforge.names.Name
|
||||||
|
import hep.dataforge.vision.Vision
|
||||||
|
|
||||||
|
public interface Output<in V : Vision> {
|
||||||
|
public fun render(vision: V)
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Page<in V : Vision> {
|
||||||
|
public fun output(name: Name, meta: Meta = Meta.EMPTY): Output<V>?
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun <V : Vision> Page<V>.render(name: Name, vision: V): Unit =
|
||||||
|
output(name)?.render(vision) ?: error("Could not resolve renderer for name $name")
|
@ -28,7 +28,8 @@ public abstract class EmptyVision : Vision {
|
|||||||
|
|
||||||
override val properties: Config? = null
|
override val properties: Config? = null
|
||||||
|
|
||||||
override fun getAllProperties(): Laminate = Laminate(Meta.EMPTY)
|
override val allProperties: Laminate
|
||||||
|
get() = Laminate(Meta.EMPTY)
|
||||||
|
|
||||||
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? = null
|
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? = null
|
||||||
|
|
||||||
|
@ -0,0 +1,52 @@
|
|||||||
|
package hep.dataforge.vision.html
|
||||||
|
|
||||||
|
import hep.dataforge.meta.configure
|
||||||
|
import hep.dataforge.meta.set
|
||||||
|
import hep.dataforge.vision.Vision
|
||||||
|
import hep.dataforge.vision.VisionBase
|
||||||
|
import hep.dataforge.vision.VisionGroup
|
||||||
|
import kotlinx.html.*
|
||||||
|
import kotlin.test.Test
|
||||||
|
|
||||||
|
class HtmlTagTest {
|
||||||
|
|
||||||
|
fun HtmlOutput<Vision>.vision(block: Vision.() -> Unit) =
|
||||||
|
outputScope.renderVision(this, VisionBase().apply(block))
|
||||||
|
|
||||||
|
val fragment = buildVisionFragment {
|
||||||
|
div {
|
||||||
|
h1 { +"Head" }
|
||||||
|
visionOutput("ddd") {
|
||||||
|
vision {
|
||||||
|
configure {
|
||||||
|
set("myProp", 82)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val simpleVisionRenderer: HtmlVisionRenderer<Vision> = { vision ->
|
||||||
|
div {
|
||||||
|
h2 { +"Properties" }
|
||||||
|
ul {
|
||||||
|
vision.properties?.items?.forEach {
|
||||||
|
li {
|
||||||
|
a { +it.key.toString() }
|
||||||
|
p { +it.value.toString() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val groupRenderer: HtmlVisionRenderer<VisionGroup> = { group ->
|
||||||
|
p { +"This is group" }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testStringRender() {
|
||||||
|
println(fragment.renderToString(simpleVisionRenderer))
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package hep.dataforge.vision.html
|
||||||
|
|
||||||
|
import hep.dataforge.vision.Vision
|
||||||
|
import kotlinx.browser.document
|
||||||
|
import kotlinx.html.TagConsumer
|
||||||
|
import org.w3c.dom.Element
|
||||||
|
import org.w3c.dom.HTMLElement
|
||||||
|
|
||||||
|
public interface HtmlVisionBinding<in V: Vision>{
|
||||||
|
public fun bind(element: Element, vision: V): Unit
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun <V: Vision> Map<String, V>.bind(binder: HtmlVisionBinding<V>){
|
||||||
|
forEach { (id, vision) ->
|
||||||
|
val element = document.getElementById(id) ?: error("Could not find element with id $id")
|
||||||
|
binder.bind(element, vision)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun HtmlVisionFragment<Vision>.bindToDocument(
|
||||||
|
root: TagConsumer<HTMLElement>,
|
||||||
|
binder: HtmlVisionBinding<Vision>,
|
||||||
|
): HTMLElement = BindingHtmlOutputScope<HTMLElement, Vision>(root).apply(layout).let { scope ->
|
||||||
|
scope.finalize().apply {
|
||||||
|
scope.bindings.forEach { (name, vision) ->
|
||||||
|
val id = scope.resolveId(name)
|
||||||
|
val element = document.getElementById(id) ?: error("Could not find element with name $name and id $id")
|
||||||
|
binder.bind(element, vision)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,20 +0,0 @@
|
|||||||
package hep.dataforge.vision.rendering
|
|
||||||
|
|
||||||
import hep.dataforge.vision.Renderer
|
|
||||||
import hep.dataforge.vision.Vision
|
|
||||||
import org.w3c.dom.HTMLElement
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A display container factory for specific vision
|
|
||||||
* @param V type of [Vision] to be rendered
|
|
||||||
* @param C the specific type of the container
|
|
||||||
*/
|
|
||||||
public fun interface HTMLVisionDisplay<in V : Vision, C : Renderer<V>> {
|
|
||||||
public fun attachRenderer(element: HTMLElement): C
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render a specific element and return container for configuration
|
|
||||||
*/
|
|
||||||
public fun <V : Vision, C : Renderer<V>> HTMLVisionDisplay<V, C>.render(element: HTMLElement, vision: V): C =
|
|
||||||
attachRenderer(element).apply { render(vision)}
|
|
@ -1,219 +1,219 @@
|
|||||||
package hep.dataforge.vision.server
|
//package hep.dataforge.vision.server
|
||||||
|
//
|
||||||
import hep.dataforge.meta.*
|
//import hep.dataforge.meta.*
|
||||||
import hep.dataforge.names.Name
|
//import hep.dataforge.names.Name
|
||||||
import hep.dataforge.names.toName
|
//import hep.dataforge.names.toName
|
||||||
import hep.dataforge.vision.server.VisionServer.Companion.DEFAULT_PAGE
|
//import hep.dataforge.vision.server.VisionServer.Companion.DEFAULT_PAGE
|
||||||
import io.ktor.application.Application
|
//import io.ktor.application.Application
|
||||||
import io.ktor.application.featureOrNull
|
//import io.ktor.application.featureOrNull
|
||||||
import io.ktor.application.install
|
//import io.ktor.application.install
|
||||||
import io.ktor.features.CORS
|
//import io.ktor.features.CORS
|
||||||
import io.ktor.http.content.resources
|
//import io.ktor.http.content.resources
|
||||||
import io.ktor.http.content.static
|
//import io.ktor.http.content.static
|
||||||
import io.ktor.routing.Routing
|
//import io.ktor.routing.Routing
|
||||||
import io.ktor.routing.route
|
//import io.ktor.routing.route
|
||||||
import io.ktor.routing.routing
|
//import io.ktor.routing.routing
|
||||||
import io.ktor.server.engine.ApplicationEngine
|
//import io.ktor.server.engine.ApplicationEngine
|
||||||
import io.ktor.websocket.WebSockets
|
//import io.ktor.websocket.WebSockets
|
||||||
import kotlinx.html.TagConsumer
|
//import kotlinx.html.TagConsumer
|
||||||
import java.awt.Desktop
|
//import java.awt.Desktop
|
||||||
import java.net.URI
|
//import java.net.URI
|
||||||
import kotlin.text.get
|
//import kotlin.text.get
|
||||||
|
//
|
||||||
public enum class ServerUpdateMode {
|
//public enum class ServerUpdateMode {
|
||||||
NONE,
|
// NONE,
|
||||||
PUSH,
|
// PUSH,
|
||||||
PULL
|
// PULL
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
public class VisionServer internal constructor(
|
//public class VisionServer internal constructor(
|
||||||
private val routing: Routing,
|
// private val routing: Routing,
|
||||||
private val rootRoute: String,
|
// private val rootRoute: String,
|
||||||
) : Configurable {
|
//) : Configurable {
|
||||||
override val config: Config = Config()
|
// override val config: Config = Config()
|
||||||
public var updateMode: ServerUpdateMode by config.enum(ServerUpdateMode.NONE, key = UPDATE_MODE_KEY)
|
// public var updateMode: ServerUpdateMode by config.enum(ServerUpdateMode.NONE, key = UPDATE_MODE_KEY)
|
||||||
public var updateInterval: Long by config.long(300, key = UPDATE_INTERVAL_KEY)
|
// public var updateInterval: Long by config.long(300, key = UPDATE_INTERVAL_KEY)
|
||||||
public var embedData: Boolean by config.boolean(false)
|
// public var embedData: Boolean by config.boolean(false)
|
||||||
|
//
|
||||||
/**
|
// /**
|
||||||
* a list of headers that should be applied to all pages
|
// * a list of headers that should be applied to all pages
|
||||||
*/
|
|
||||||
private val globalHeaders: ArrayList<HtmlFragment> = ArrayList<HtmlFragment>()
|
|
||||||
|
|
||||||
public fun header(block: TagConsumer<*>.() -> Unit) {
|
|
||||||
globalHeaders.add(HtmlFragment(block))
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun page(
|
|
||||||
plotlyFragment: PlotlyFragment,
|
|
||||||
route: String = DEFAULT_PAGE,
|
|
||||||
title: String = "Plotly server page '$route'",
|
|
||||||
headers: List<HtmlFragment> = emptyList(),
|
|
||||||
) {
|
|
||||||
routing.createRouteFromPath(rootRoute).apply {
|
|
||||||
val plots = HashMap<String, Plot>()
|
|
||||||
route(route) {
|
|
||||||
//Update websocket
|
|
||||||
webSocket("ws/{id}") {
|
|
||||||
val plotId: String = call.parameters["id"] ?: error("Plot id not defined")
|
|
||||||
|
|
||||||
application.log.debug("Opened server socket for $plotId")
|
|
||||||
|
|
||||||
val plot = plots[plotId] ?: error("Plot with id='$plotId' not registered")
|
|
||||||
|
|
||||||
try {
|
|
||||||
plot.collectUpdates(plotId, this, updateInterval).collect { update ->
|
|
||||||
val json = update.toJson()
|
|
||||||
outgoing.send(Frame.Text(json.toString()))
|
|
||||||
}
|
|
||||||
} catch (ex: Exception) {
|
|
||||||
application.log.debug("Closed server socket for $plotId")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//Plots in their json representation
|
|
||||||
get("data/{id}") {
|
|
||||||
val id: String = call.parameters["id"] ?: error("Plot id not defined")
|
|
||||||
|
|
||||||
val plot: Plot? = plots[id]
|
|
||||||
if (plot == null) {
|
|
||||||
call.respond(HttpStatusCode.NotFound, "Plot with id = $id not found")
|
|
||||||
} else {
|
|
||||||
call.respondText(
|
|
||||||
plot.toJsonString(),
|
|
||||||
contentType = ContentType.Application.Json,
|
|
||||||
status = HttpStatusCode.OK
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//filled pages
|
|
||||||
get {
|
|
||||||
val origin = call.request.origin
|
|
||||||
val url = URLBuilder().apply {
|
|
||||||
protocol = URLProtocol.createOrDefault(origin.scheme)
|
|
||||||
//workaround for https://github.com/ktorio/ktor/issues/1663
|
|
||||||
host = if (origin.host.startsWith("0:")) "[${origin.host}]" else origin.host
|
|
||||||
port = origin.port
|
|
||||||
encodedPath = origin.uri
|
|
||||||
}.build()
|
|
||||||
call.respondHtml {
|
|
||||||
val normalizedRoute = if (rootRoute.endsWith("/")) {
|
|
||||||
rootRoute
|
|
||||||
} else {
|
|
||||||
"$rootRoute/"
|
|
||||||
}
|
|
||||||
|
|
||||||
head {
|
|
||||||
meta {
|
|
||||||
charset = "utf-8"
|
|
||||||
(globalHeaders + headers).forEach {
|
|
||||||
it.visit(consumer)
|
|
||||||
}
|
|
||||||
script {
|
|
||||||
type = "text/javascript"
|
|
||||||
src = "${normalizedRoute}js/plotly.min.js"
|
|
||||||
}
|
|
||||||
script {
|
|
||||||
type = "text/javascript"
|
|
||||||
src = "${normalizedRoute}js/plotlyConnect.js"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
title(title)
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
val container =
|
|
||||||
ServerPlotlyRenderer(url, updateMode, updateInterval, embedData) { plotId, plot ->
|
|
||||||
plots[plotId] = plot
|
|
||||||
}
|
|
||||||
with(plotlyFragment) {
|
|
||||||
render(container)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun page(
|
|
||||||
route: String = DEFAULT_PAGE,
|
|
||||||
title: String = "Plotly server page '$route'",
|
|
||||||
headers: List<HtmlFragment> = emptyList(),
|
|
||||||
content: FlowContent.(renderer: PlotlyRenderer) -> Unit,
|
|
||||||
) {
|
|
||||||
page(PlotlyFragment(content), route, title, headers)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public companion object {
|
|
||||||
public const val DEFAULT_PAGE: String = "/"
|
|
||||||
public val UPDATE_MODE_KEY: Name = "update.mode".toName()
|
|
||||||
public val UPDATE_INTERVAL_KEY: Name = "update.interval".toName()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attach plotly application to given server
|
|
||||||
*/
|
|
||||||
public fun Application.visionModule(route: String = DEFAULT_PAGE): VisionServer {
|
|
||||||
if (featureOrNull(WebSockets) == null) {
|
|
||||||
install(WebSockets)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (featureOrNull(CORS) == null) {
|
|
||||||
install(CORS) {
|
|
||||||
anyHost()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
val routing = routing {
|
|
||||||
route(route) {
|
|
||||||
static {
|
|
||||||
resources()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return VisionServer(routing, route)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configure server to start sending updates in push mode. Does not affect loaded pages
|
|
||||||
*/
|
|
||||||
public fun VisionServer.pushUpdates(interval: Long = 100): VisionServer = apply {
|
|
||||||
updateMode = ServerUpdateMode.PUSH
|
|
||||||
updateInterval = interval
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configure client to request regular updates from server. Pull updates are more expensive than push updates since
|
|
||||||
* they contain the full plot data and server can't decide what to send.
|
|
||||||
*/
|
|
||||||
public fun VisionServer.pullUpdates(interval: Long = 1000): VisionServer = apply {
|
|
||||||
updateMode = ServerUpdateMode.PULL
|
|
||||||
updateInterval = interval
|
|
||||||
}
|
|
||||||
|
|
||||||
///**
|
|
||||||
// * Start static server (updates via reload)
|
|
||||||
// */
|
// */
|
||||||
//@OptIn(KtorExperimentalAPI::class)
|
// private val globalHeaders: ArrayList<HtmlFragment> = ArrayList<HtmlFragment>()
|
||||||
//public fun Plotly.serve(
|
//
|
||||||
// scope: CoroutineScope = GlobalScope,
|
// public fun header(block: TagConsumer<*>.() -> Unit) {
|
||||||
// host: String = "localhost",
|
// globalHeaders.add(HtmlFragment(block))
|
||||||
// port: Int = 7777,
|
// }
|
||||||
// block: PlotlyServer.() -> Unit,
|
//
|
||||||
//): ApplicationEngine = scope.embeddedServer(io.ktor.server.cio.CIO, port, host) {
|
// public fun page(
|
||||||
// plotlyModule().apply(block)
|
// plotlyFragment: PlotlyFragment,
|
||||||
//}.start()
|
// route: String = DEFAULT_PAGE,
|
||||||
|
// title: String = "Plotly server page '$route'",
|
||||||
|
// headers: List<HtmlFragment> = emptyList(),
|
||||||
public fun ApplicationEngine.show() {
|
// ) {
|
||||||
val connector = environment.connectors.first()
|
// routing.createRouteFromPath(rootRoute).apply {
|
||||||
val uri = URI("http", null, connector.host, connector.port, null, null, null)
|
// val plots = HashMap<String, Plot>()
|
||||||
Desktop.getDesktop().browse(uri)
|
// route(route) {
|
||||||
}
|
// //Update websocket
|
||||||
|
// webSocket("ws/{id}") {
|
||||||
public fun ApplicationEngine.close(): Unit = stop(1000, 5000)
|
// val plotId: String = call.parameters["id"] ?: error("Plot id not defined")
|
||||||
|
//
|
||||||
|
// application.log.debug("Opened server socket for $plotId")
|
||||||
|
//
|
||||||
|
// val plot = plots[plotId] ?: error("Plot with id='$plotId' not registered")
|
||||||
|
//
|
||||||
|
// try {
|
||||||
|
// plot.collectUpdates(plotId, this, updateInterval).collect { update ->
|
||||||
|
// val json = update.toJson()
|
||||||
|
// outgoing.send(Frame.Text(json.toString()))
|
||||||
|
// }
|
||||||
|
// } catch (ex: Exception) {
|
||||||
|
// application.log.debug("Closed server socket for $plotId")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// //Plots in their json representation
|
||||||
|
// get("data/{id}") {
|
||||||
|
// val id: String = call.parameters["id"] ?: error("Plot id not defined")
|
||||||
|
//
|
||||||
|
// val plot: Plot? = plots[id]
|
||||||
|
// if (plot == null) {
|
||||||
|
// call.respond(HttpStatusCode.NotFound, "Plot with id = $id not found")
|
||||||
|
// } else {
|
||||||
|
// call.respondText(
|
||||||
|
// plot.toJsonString(),
|
||||||
|
// contentType = ContentType.Application.Json,
|
||||||
|
// status = HttpStatusCode.OK
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// //filled pages
|
||||||
|
// get {
|
||||||
|
// val origin = call.request.origin
|
||||||
|
// val url = URLBuilder().apply {
|
||||||
|
// protocol = URLProtocol.createOrDefault(origin.scheme)
|
||||||
|
// //workaround for https://github.com/ktorio/ktor/issues/1663
|
||||||
|
// host = if (origin.host.startsWith("0:")) "[${origin.host}]" else origin.host
|
||||||
|
// port = origin.port
|
||||||
|
// encodedPath = origin.uri
|
||||||
|
// }.build()
|
||||||
|
// call.respondHtml {
|
||||||
|
// val normalizedRoute = if (rootRoute.endsWith("/")) {
|
||||||
|
// rootRoute
|
||||||
|
// } else {
|
||||||
|
// "$rootRoute/"
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// head {
|
||||||
|
// meta {
|
||||||
|
// charset = "utf-8"
|
||||||
|
// (globalHeaders + headers).forEach {
|
||||||
|
// it.visit(consumer)
|
||||||
|
// }
|
||||||
|
// script {
|
||||||
|
// type = "text/javascript"
|
||||||
|
// src = "${normalizedRoute}js/plotly.min.js"
|
||||||
|
// }
|
||||||
|
// script {
|
||||||
|
// type = "text/javascript"
|
||||||
|
// src = "${normalizedRoute}js/plotlyConnect.js"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// title(title)
|
||||||
|
// }
|
||||||
|
// body {
|
||||||
|
// val container =
|
||||||
|
// ServerPlotlyRenderer(url, updateMode, updateInterval, embedData) { plotId, plot ->
|
||||||
|
// plots[plotId] = plot
|
||||||
|
// }
|
||||||
|
// with(plotlyFragment) {
|
||||||
|
// render(container)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public fun page(
|
||||||
|
// route: String = DEFAULT_PAGE,
|
||||||
|
// title: String = "Plotly server page '$route'",
|
||||||
|
// headers: List<HtmlFragment> = emptyList(),
|
||||||
|
// content: FlowContent.(renderer: PlotlyRenderer) -> Unit,
|
||||||
|
// ) {
|
||||||
|
// page(PlotlyFragment(content), route, title, headers)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// public companion object {
|
||||||
|
// public const val DEFAULT_PAGE: String = "/"
|
||||||
|
// public val UPDATE_MODE_KEY: Name = "update.mode".toName()
|
||||||
|
// public val UPDATE_INTERVAL_KEY: Name = "update.interval".toName()
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//
|
||||||
|
///**
|
||||||
|
// * Attach plotly application to given server
|
||||||
|
// */
|
||||||
|
//public fun Application.visionModule(route: String = DEFAULT_PAGE): VisionServer {
|
||||||
|
// if (featureOrNull(WebSockets) == null) {
|
||||||
|
// install(WebSockets)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (featureOrNull(CORS) == null) {
|
||||||
|
// install(CORS) {
|
||||||
|
// anyHost()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// val routing = routing {
|
||||||
|
// route(route) {
|
||||||
|
// static {
|
||||||
|
// resources()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return VisionServer(routing, route)
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//
|
||||||
|
///**
|
||||||
|
// * Configure server to start sending updates in push mode. Does not affect loaded pages
|
||||||
|
// */
|
||||||
|
//public fun VisionServer.pushUpdates(interval: Long = 100): VisionServer = apply {
|
||||||
|
// updateMode = ServerUpdateMode.PUSH
|
||||||
|
// updateInterval = interval
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
///**
|
||||||
|
// * Configure client to request regular updates from server. Pull updates are more expensive than push updates since
|
||||||
|
// * they contain the full plot data and server can't decide what to send.
|
||||||
|
// */
|
||||||
|
//public fun VisionServer.pullUpdates(interval: Long = 1000): VisionServer = apply {
|
||||||
|
// updateMode = ServerUpdateMode.PULL
|
||||||
|
// updateInterval = interval
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
/////**
|
||||||
|
//// * Start static server (updates via reload)
|
||||||
|
//// */
|
||||||
|
////@OptIn(KtorExperimentalAPI::class)
|
||||||
|
////public fun Plotly.serve(
|
||||||
|
//// scope: CoroutineScope = GlobalScope,
|
||||||
|
//// host: String = "localhost",
|
||||||
|
//// port: Int = 7777,
|
||||||
|
//// block: PlotlyServer.() -> Unit,
|
||||||
|
////): ApplicationEngine = scope.embeddedServer(io.ktor.server.cio.CIO, port, host) {
|
||||||
|
//// plotlyModule().apply(block)
|
||||||
|
////}.start()
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//public fun ApplicationEngine.show() {
|
||||||
|
// val connector = environment.connectors.first()
|
||||||
|
// val uri = URI("http", null, connector.host, connector.port, null, null, null)
|
||||||
|
// Desktop.getDesktop().browse(uri)
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//public fun ApplicationEngine.close(): Unit = stop(1000, 5000)
|
@ -1,70 +0,0 @@
|
|||||||
package hep.dataforge.vision.server
|
|
||||||
|
|
||||||
import kotlinx.html.*
|
|
||||||
import kotlinx.html.stream.createHTML
|
|
||||||
import java.nio.file.Files
|
|
||||||
import java.nio.file.Path
|
|
||||||
import java.nio.file.StandardOpenOption
|
|
||||||
|
|
||||||
public class HtmlFragment(public val visit: TagConsumer<*>.() -> Unit) {
|
|
||||||
override fun toString(): String {
|
|
||||||
return createHTML().also(visit).finalize()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public operator fun HtmlFragment.plus(other: HtmlFragment): HtmlFragment = HtmlFragment {
|
|
||||||
this@plus.run { visit() }
|
|
||||||
other.run { visit() }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the asset exists in given local location and put it there if it does not
|
|
||||||
*/
|
|
||||||
internal fun checkOrStoreFile(basePath: Path, filePath: Path, resource: String): Path {
|
|
||||||
val fullPath = basePath.resolveSibling(filePath).toAbsolutePath()
|
|
||||||
|
|
||||||
if (Files.exists(fullPath)) {
|
|
||||||
//TODO checksum
|
|
||||||
} else {
|
|
||||||
//TODO add logging
|
|
||||||
|
|
||||||
val bytes = HtmlFragment::class.java.getResourceAsStream(resource).readAllBytes()
|
|
||||||
Files.createDirectories(fullPath.parent)
|
|
||||||
Files.write(fullPath, bytes, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)
|
|
||||||
}
|
|
||||||
|
|
||||||
return if (basePath.isAbsolute && fullPath.startsWith(basePath)) {
|
|
||||||
basePath.relativize(fullPath)
|
|
||||||
} else {
|
|
||||||
filePath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A header that automatically copies relevant scripts to given path
|
|
||||||
*/
|
|
||||||
public fun localScriptHeader(
|
|
||||||
basePath: Path,
|
|
||||||
scriptPath: Path,
|
|
||||||
resource: String
|
|
||||||
): HtmlFragment = HtmlFragment {
|
|
||||||
val relativePath = checkOrStoreFile(basePath, scriptPath, resource)
|
|
||||||
script {
|
|
||||||
type = "text/javascript"
|
|
||||||
src = relativePath.toString()
|
|
||||||
attributes["onload"] = "console.log('Script successfully loaded from $relativePath')"
|
|
||||||
attributes["onerror"] = "console.log('Failed to load script from $relativePath')"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun localCssHeader(
|
|
||||||
basePath: Path,
|
|
||||||
cssPath: Path,
|
|
||||||
resource: String
|
|
||||||
): HtmlFragment = HtmlFragment {
|
|
||||||
val relativePath = checkOrStoreFile(basePath, cssPath, resource)
|
|
||||||
link {
|
|
||||||
rel = "stylesheet"
|
|
||||||
href = relativePath.toString()
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,7 +7,7 @@ import hep.dataforge.names.Name
|
|||||||
import hep.dataforge.names.asName
|
import hep.dataforge.names.asName
|
||||||
import hep.dataforge.names.plus
|
import hep.dataforge.names.plus
|
||||||
import hep.dataforge.vision.VisionContainerBuilder
|
import hep.dataforge.vision.VisionContainerBuilder
|
||||||
import hep.dataforge.vision.properties
|
import hep.dataforge.vision.props
|
||||||
import hep.dataforge.vision.set
|
import hep.dataforge.vision.set
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
@ -18,7 +18,7 @@ import kotlinx.serialization.UseSerializers
|
|||||||
public class PolyLine(public var points: List<Point3D>) : BasicSolid(), Solid {
|
public class PolyLine(public var points: List<Point3D>) : BasicSolid(), Solid {
|
||||||
|
|
||||||
//var lineType by string()
|
//var lineType by string()
|
||||||
public var thickness: Number by properties().number(1.0, key = SolidMaterial.MATERIAL_KEY + THICKNESS_KEY)
|
public var thickness: Number by props().number(1.0, key = SolidMaterial.MATERIAL_KEY + THICKNESS_KEY)
|
||||||
|
|
||||||
public companion object {
|
public companion object {
|
||||||
public val THICKNESS_KEY: Name = "thickness".asName()
|
public val THICKNESS_KEY: Name = "thickness".asName()
|
||||||
|
@ -7,10 +7,10 @@ import hep.dataforge.names.asName
|
|||||||
import hep.dataforge.names.plus
|
import hep.dataforge.names.plus
|
||||||
import hep.dataforge.values.ValueType
|
import hep.dataforge.values.ValueType
|
||||||
import hep.dataforge.values.asValue
|
import hep.dataforge.values.asValue
|
||||||
import hep.dataforge.vision.Renderer
|
|
||||||
import hep.dataforge.vision.Vision
|
import hep.dataforge.vision.Vision
|
||||||
import hep.dataforge.vision.Vision.Companion.VISIBLE_KEY
|
import hep.dataforge.vision.Vision.Companion.VISIBLE_KEY
|
||||||
import hep.dataforge.vision.enum
|
import hep.dataforge.vision.enum
|
||||||
|
import hep.dataforge.vision.layout.Output
|
||||||
import hep.dataforge.vision.setProperty
|
import hep.dataforge.vision.setProperty
|
||||||
import hep.dataforge.vision.solid.Solid.Companion.DETAIL_KEY
|
import hep.dataforge.vision.solid.Solid.Companion.DETAIL_KEY
|
||||||
import hep.dataforge.vision.solid.Solid.Companion.IGNORE_KEY
|
import hep.dataforge.vision.solid.Solid.Companion.IGNORE_KEY
|
||||||
@ -104,7 +104,7 @@ public var Solid.layer: Int
|
|||||||
config[LAYER_KEY] = value.asValue()
|
config[LAYER_KEY] = value.asValue()
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun Renderer<Solid>.render(action: SolidGroup.() -> Unit): Unit = render(SolidGroup().apply(action))
|
public fun Output<Solid>.solidGroup(builder: SolidGroup.() -> Unit): Unit = render(SolidGroup().apply(builder))
|
||||||
|
|
||||||
// Common properties
|
// Common properties
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ import hep.dataforge.vision.Vision
|
|||||||
import hep.dataforge.vision.VisionGroup
|
import hep.dataforge.vision.VisionGroup
|
||||||
import hep.dataforge.vision.VisionGroupBase
|
import hep.dataforge.vision.VisionGroupBase
|
||||||
import hep.dataforge.vision.VisionManager
|
import hep.dataforge.vision.VisionManager
|
||||||
import hep.dataforge.vision.VisionManager.Companion.VISION_SERIAL_MODULE_TARGET
|
import hep.dataforge.vision.VisionManager.Companion.VISION_SERIALIZER_MODULE_TARGET
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.modules.*
|
import kotlinx.serialization.modules.*
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
@ -24,7 +24,7 @@ public class SolidManager(meta: Meta) : AbstractPlugin(meta) {
|
|||||||
override val tag: PluginTag get() = Companion.tag
|
override val tag: PluginTag get() = Companion.tag
|
||||||
|
|
||||||
override fun content(target: String): Map<Name, Any> = when (target) {
|
override fun content(target: String): Map<Name, Any> = when (target) {
|
||||||
VISION_SERIAL_MODULE_TARGET -> mapOf(tag.name.toName() to serializersModuleForSolids)
|
VISION_SERIALIZER_MODULE_TARGET -> mapOf(tag.name.toName() to serializersModuleForSolids)
|
||||||
else -> super.content(target)
|
else -> super.content(target)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,11 +60,7 @@ public class SolidManager(meta: Meta) : AbstractPlugin(meta) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal val jsonForSolids: Json = Json{
|
internal val jsonForSolids: Json = Json(VisionManager.defaultJson){
|
||||||
prettyPrint = true
|
|
||||||
useArrayPolymorphism = false
|
|
||||||
encodeDefaults = false
|
|
||||||
ignoreUnknownKeys = true
|
|
||||||
serializersModule = serializersModuleForSolids
|
serializersModule = serializersModuleForSolids
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,7 +54,6 @@ public class SolidMaterial : Scheme() {
|
|||||||
NodeDescriptor {
|
NodeDescriptor {
|
||||||
value(COLOR_KEY) {
|
value(COLOR_KEY) {
|
||||||
type(ValueType.STRING, ValueType.NUMBER)
|
type(ValueType.STRING, ValueType.NUMBER)
|
||||||
default("#ffffff")
|
|
||||||
widgetType = "color"
|
widgetType = "color"
|
||||||
}
|
}
|
||||||
value(OPACITY_KEY) {
|
value(OPACITY_KEY) {
|
||||||
|
@ -12,22 +12,14 @@ import kotlin.collections.set
|
|||||||
public abstract class AbstractReference : BasicSolid(), VisionGroup {
|
public abstract class AbstractReference : BasicSolid(), VisionGroup {
|
||||||
public abstract val prototype: Solid
|
public abstract val prototype: Solid
|
||||||
|
|
||||||
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? {
|
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? = sequence {
|
||||||
return if (inherit) {
|
|
||||||
sequence {
|
|
||||||
yield(properties?.get(name))
|
yield(properties?.get(name))
|
||||||
yieldAll(getStyleItems(name))
|
yieldAll(getStyleItems(name))
|
||||||
yield(prototype.getProperty(name))
|
yield(prototype.getProperty(name))
|
||||||
|
if (inherit) {
|
||||||
yield(parent?.getProperty(name, inherit))
|
yield(parent?.getProperty(name, inherit))
|
||||||
}.merge()
|
|
||||||
} else {
|
|
||||||
sequence {
|
|
||||||
yield(properties?.get(name))
|
|
||||||
yieldAll(getStyleItems(name))
|
|
||||||
yield(prototype.getProperty(name, false))
|
|
||||||
}.merge()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}.merge()
|
||||||
|
|
||||||
override var styles: List<String>
|
override var styles: List<String>
|
||||||
get() = (properties[Vision.STYLE_KEY]?.stringList ?: emptyList()) + prototype.styles
|
get() = (properties[Vision.STYLE_KEY]?.stringList ?: emptyList()) + prototype.styles
|
||||||
@ -35,8 +27,13 @@ public abstract class AbstractReference : BasicSolid(), VisionGroup {
|
|||||||
config[Vision.STYLE_KEY] = value
|
config[Vision.STYLE_KEY] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getAllProperties(): Laminate =
|
override val allProperties: Laminate
|
||||||
Laminate(properties, allStyles, prototype.getAllProperties(), parent?.getAllProperties())
|
get() = Laminate(
|
||||||
|
properties,
|
||||||
|
allStyles,
|
||||||
|
prototype.allProperties,
|
||||||
|
parent?.allProperties,
|
||||||
|
)
|
||||||
|
|
||||||
override fun attachChildren() {
|
override fun attachChildren() {
|
||||||
//do nothing
|
//do nothing
|
||||||
|
@ -6,7 +6,7 @@ import hep.dataforge.names.Name
|
|||||||
import hep.dataforge.names.plus
|
import hep.dataforge.names.plus
|
||||||
import hep.dataforge.names.toName
|
import hep.dataforge.names.toName
|
||||||
import hep.dataforge.vision.Colors
|
import hep.dataforge.vision.Colors
|
||||||
import hep.dataforge.vision.Renderer
|
import hep.dataforge.vision.layout.Output
|
||||||
import hep.dataforge.vision.solid.Solid
|
import hep.dataforge.vision.solid.Solid
|
||||||
import hep.dataforge.vision.solid.specifications.*
|
import hep.dataforge.vision.solid.specifications.*
|
||||||
import hep.dataforge.vision.solid.three.ThreeMaterials.HIGHLIGHT_MATERIAL
|
import hep.dataforge.vision.solid.three.ThreeMaterials.HIGHLIGHT_MATERIAL
|
||||||
@ -24,12 +24,9 @@ import info.laht.threekt.materials.LineBasicMaterial
|
|||||||
import info.laht.threekt.math.Vector2
|
import info.laht.threekt.math.Vector2
|
||||||
import info.laht.threekt.objects.LineSegments
|
import info.laht.threekt.objects.LineSegments
|
||||||
import info.laht.threekt.objects.Mesh
|
import info.laht.threekt.objects.Mesh
|
||||||
import info.laht.threekt.renderers.WebGLRenderer
|
|
||||||
import info.laht.threekt.scenes.Scene
|
import info.laht.threekt.scenes.Scene
|
||||||
import kotlinx.browser.window
|
import org.w3c.dom.Element
|
||||||
import kotlinx.dom.clear
|
|
||||||
import org.w3c.dom.HTMLCanvasElement
|
import org.w3c.dom.HTMLCanvasElement
|
||||||
import org.w3c.dom.HTMLElement
|
|
||||||
import org.w3c.dom.Node
|
import org.w3c.dom.Node
|
||||||
import org.w3c.dom.events.MouseEvent
|
import org.w3c.dom.events.MouseEvent
|
||||||
import kotlin.math.cos
|
import kotlin.math.cos
|
||||||
@ -41,7 +38,7 @@ import kotlin.math.sin
|
|||||||
public class ThreeCanvas(
|
public class ThreeCanvas(
|
||||||
public val three: ThreePlugin,
|
public val three: ThreePlugin,
|
||||||
public val options: Canvas3DOptions,
|
public val options: Canvas3DOptions,
|
||||||
) : Renderer<Solid> {
|
) : Output<Solid> {
|
||||||
private var root: Object3D? = null
|
private var root: Object3D? = null
|
||||||
|
|
||||||
private val raycaster = Raycaster()
|
private val raycaster = Raycaster()
|
||||||
@ -62,55 +59,60 @@ public class ThreeCanvas(
|
|||||||
|
|
||||||
private var picked: Object3D? = null
|
private var picked: Object3D? = null
|
||||||
|
|
||||||
|
private val renderer = WebGLRenderer { antialias = true }.apply {
|
||||||
|
setClearColor(Colors.skyblue, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
public val canvas: HTMLCanvasElement = renderer.domElement as HTMLCanvasElement
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attach canvas to given [HTMLElement]
|
* Force camera aspect ration and renderer size recalculation
|
||||||
*/
|
*/
|
||||||
public fun attach(element: HTMLElement) {
|
public fun updateSize() {
|
||||||
fun WebGLRenderer.resize() {
|
val width = canvas.clientWidth
|
||||||
val canvas = domElement as HTMLCanvasElement
|
val height = canvas.clientHeight
|
||||||
|
renderer.setSize(width, height, false)
|
||||||
val width = options.computeWidth(canvas.clientWidth)
|
camera.aspect = width.toDouble() / height.toDouble()
|
||||||
val height = options.computeHeight(canvas.clientHeight)
|
|
||||||
|
|
||||||
canvas.width = width
|
|
||||||
canvas.height = height
|
|
||||||
|
|
||||||
setSize(width, height, false)
|
|
||||||
camera.aspect = width.toDouble() / height
|
|
||||||
camera.updateProjectionMatrix()
|
camera.updateProjectionMatrix()
|
||||||
}
|
}
|
||||||
|
|
||||||
element.clear()
|
/**
|
||||||
|
* Attach canvas to given [HTMLElement]
|
||||||
//Attach listener to track mouse changes
|
*/
|
||||||
element.addEventListener("mousemove", { event ->
|
init {
|
||||||
(event as? MouseEvent)?.run {
|
canvas.addEventListener("pointerdown", {
|
||||||
val rect = element.getBoundingClientRect()
|
|
||||||
mousePosition.x = ((event.clientX - rect.left) / element.clientWidth) * 2 - 1
|
|
||||||
mousePosition.y = -((event.clientY - rect.top) / element.clientHeight) * 2 + 1
|
|
||||||
}
|
|
||||||
}, false)
|
|
||||||
|
|
||||||
element.addEventListener("mousedown", {
|
|
||||||
val picked = pick()
|
val picked = pick()
|
||||||
options.onSelect?.invoke(picked?.fullName())
|
options.onSelect?.invoke(picked?.fullName())
|
||||||
}, false)
|
}, false)
|
||||||
|
|
||||||
val renderer = WebGLRenderer { antialias = true }.apply {
|
//Attach listener to track mouse changes
|
||||||
setClearColor(Colors.skyblue, 1)
|
canvas.addEventListener("mousemove", { event ->
|
||||||
|
(event as? MouseEvent)?.run {
|
||||||
|
val rect = canvas.getBoundingClientRect()
|
||||||
|
mousePosition.x = ((event.clientX - rect.left) / canvas.clientWidth) * 2 - 1
|
||||||
|
mousePosition.y = -((event.clientY - rect.top) / canvas.clientHeight) * 2 + 1
|
||||||
}
|
}
|
||||||
|
}, false)
|
||||||
|
|
||||||
val canvas = renderer.domElement as HTMLCanvasElement
|
|
||||||
|
|
||||||
canvas.style.apply {
|
canvas.style.apply {
|
||||||
width = "100%"
|
width = "100%"
|
||||||
|
minWidth = "${options.minWith.toInt()}px"
|
||||||
|
maxWidth = "${options.maxWith.toInt()}px"
|
||||||
height = "100%"
|
height = "100%"
|
||||||
|
minHeight = "${options.minHeight.toInt()}px"
|
||||||
|
maxHeight = "${options.maxHeight.toInt()}px"
|
||||||
display = "block"
|
display = "block"
|
||||||
}
|
}
|
||||||
|
|
||||||
addControls(renderer.domElement, options.controls)
|
|
||||||
|
|
||||||
fun animate() {
|
canvas.onresize = {
|
||||||
|
updateSize()
|
||||||
|
}
|
||||||
|
|
||||||
|
addControls(canvas, options.controls)
|
||||||
|
|
||||||
|
renderer.setAnimationLoop {
|
||||||
val picked = pick()
|
val picked = pick()
|
||||||
|
|
||||||
if (picked != null && this.picked != picked) {
|
if (picked != null && this.picked != picked) {
|
||||||
@ -119,21 +121,13 @@ public class ThreeCanvas(
|
|||||||
this.picked = picked
|
this.picked = picked
|
||||||
}
|
}
|
||||||
|
|
||||||
window.requestAnimationFrame {
|
|
||||||
animate()
|
|
||||||
}
|
|
||||||
|
|
||||||
renderer.render(scene, camera)
|
renderer.render(scene, camera)
|
||||||
}
|
}
|
||||||
|
|
||||||
element.appendChild(renderer.domElement)
|
|
||||||
renderer.resize()
|
|
||||||
|
|
||||||
element.onresize = {
|
|
||||||
renderer.resize()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
animate()
|
public fun attach(element: Element) {
|
||||||
|
element.appendChild(canvas)
|
||||||
|
updateSize()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -41,7 +41,7 @@ public object ThreeMaterials {
|
|||||||
linewidth = meta["thickness"].double ?: 1.0
|
linewidth = meta["thickness"].double ?: 1.0
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getLineMaterial(meta: Meta?, cache: Boolean): LineBasicMaterial {
|
public fun getLineMaterial(meta: Meta?, cache: Boolean): LineBasicMaterial {
|
||||||
if (meta == null) return DEFAULT_LINE
|
if (meta == null) return DEFAULT_LINE
|
||||||
return if (cache) {
|
return if (cache) {
|
||||||
lineMaterialCache.getOrPut(meta) { buildLineMaterial(meta) }
|
lineMaterialCache.getOrPut(meta) { buildLineMaterial(meta) }
|
||||||
@ -73,7 +73,7 @@ public object ThreeMaterials {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getMaterial(vision3D: Vision, cache: Boolean): Material {
|
public fun getMaterial(vision3D: Vision, cache: Boolean): Material {
|
||||||
val meta = vision3D.getProperty(SolidMaterial.MATERIAL_KEY).node ?: return DEFAULT
|
val meta = vision3D.getProperty(SolidMaterial.MATERIAL_KEY).node ?: return DEFAULT
|
||||||
return if (cache) {
|
return if (cache) {
|
||||||
materialCache.getOrPut(meta) { buildMaterial(meta) }
|
materialCache.getOrPut(meta) { buildMaterial(meta) }
|
||||||
@ -87,7 +87,7 @@ public object ThreeMaterials {
|
|||||||
/**
|
/**
|
||||||
* Infer color based on meta item
|
* Infer color based on meta item
|
||||||
*/
|
*/
|
||||||
fun MetaItem<*>.getColor(): Color {
|
public fun MetaItem<*>.getColor(): Color {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
is MetaItem.ValueItem -> if (this.value.type == ValueType.NUMBER) {
|
is MetaItem.ValueItem -> if (this.value.type == ValueType.NUMBER) {
|
||||||
val int = value.number.toInt()
|
val int = value.number.toInt()
|
||||||
|
@ -6,17 +6,18 @@ import hep.dataforge.meta.empty
|
|||||||
import hep.dataforge.meta.invoke
|
import hep.dataforge.meta.invoke
|
||||||
import hep.dataforge.names.*
|
import hep.dataforge.names.*
|
||||||
import hep.dataforge.vision.Vision
|
import hep.dataforge.vision.Vision
|
||||||
import hep.dataforge.vision.rendering.HTMLVisionDisplay
|
import hep.dataforge.vision.html.HtmlVisionBinding
|
||||||
import hep.dataforge.vision.solid.*
|
import hep.dataforge.vision.solid.*
|
||||||
import hep.dataforge.vision.solid.specifications.Canvas3DOptions
|
import hep.dataforge.vision.solid.specifications.Canvas3DOptions
|
||||||
import hep.dataforge.vision.visible
|
import hep.dataforge.vision.visible
|
||||||
import info.laht.threekt.core.Object3D
|
import info.laht.threekt.core.Object3D
|
||||||
|
import org.w3c.dom.Element
|
||||||
import org.w3c.dom.HTMLElement
|
import org.w3c.dom.HTMLElement
|
||||||
import kotlin.collections.set
|
import kotlin.collections.set
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
import info.laht.threekt.objects.Group as ThreeGroup
|
import info.laht.threekt.objects.Group as ThreeGroup
|
||||||
|
|
||||||
public class ThreePlugin : AbstractPlugin(), HTMLVisionDisplay<Solid, ThreeCanvas> {
|
public class ThreePlugin : AbstractPlugin(), HtmlVisionBinding<Solid> {
|
||||||
override val tag: PluginTag get() = Companion.tag
|
override val tag: PluginTag get() = Companion.tag
|
||||||
|
|
||||||
public val solidManager: SolidManager by require(SolidManager)
|
public val solidManager: SolidManager by require(SolidManager)
|
||||||
@ -115,10 +116,15 @@ public class ThreePlugin : AbstractPlugin(), HTMLVisionDisplay<Solid, ThreeCanva
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun attachRenderer(element: HTMLElement): ThreeCanvas {
|
public fun createCanvas(
|
||||||
return ThreeCanvas(this, Canvas3DOptions.empty()).apply {
|
element: HTMLElement,
|
||||||
|
options: Canvas3DOptions = Canvas3DOptions.empty(),
|
||||||
|
): ThreeCanvas = ThreeCanvas(this, options).apply {
|
||||||
attach(element)
|
attach(element)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun bind(element: Element, vision: Solid) {
|
||||||
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
public companion object : PluginFactory<ThreePlugin> {
|
public companion object : PluginFactory<ThreePlugin> {
|
||||||
@ -128,16 +134,11 @@ public class ThreePlugin : AbstractPlugin(), HTMLVisionDisplay<Solid, ThreeCanva
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun ThreePlugin.attachRenderer(
|
|
||||||
element: HTMLElement,
|
|
||||||
options: Canvas3DOptions = Canvas3DOptions.empty(),
|
|
||||||
): ThreeCanvas = ThreeCanvas(this, options).apply { attach(element) }
|
|
||||||
|
|
||||||
public fun ThreePlugin.render(
|
public fun ThreePlugin.render(
|
||||||
element: HTMLElement,
|
element: HTMLElement,
|
||||||
obj: Solid,
|
obj: Solid,
|
||||||
options: Canvas3DOptions.() -> Unit = {},
|
options: Canvas3DOptions.() -> Unit = {},
|
||||||
): ThreeCanvas = attachRenderer(element, Canvas3DOptions(options)).apply { render(obj) }
|
): ThreeCanvas = createCanvas(element, Canvas3DOptions(options)).apply { render(obj) }
|
||||||
|
|
||||||
internal operator fun Object3D.set(token: NameToken, object3D: Object3D) {
|
internal operator fun Object3D.set(token: NameToken, object3D: Object3D) {
|
||||||
object3D.name = token.toString()
|
object3D.name = token.toString()
|
||||||
|
@ -64,7 +64,7 @@ external class WebGLRenderer(params: WebGLRendererParams = definedExternally) {
|
|||||||
fun clear(
|
fun clear(
|
||||||
color: Boolean = definedExternally,
|
color: Boolean = definedExternally,
|
||||||
depth: Boolean = definedExternally,
|
depth: Boolean = definedExternally,
|
||||||
stencil: Boolean = definedExternally
|
stencil: Boolean = definedExternally,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun clearColor()
|
fun clearColor()
|
||||||
@ -84,6 +84,11 @@ external class WebGLRenderer(params: WebGLRendererParams = definedExternally) {
|
|||||||
*/
|
*/
|
||||||
fun setClearColor(color: Int, alpha: Number)
|
fun setClearColor(color: Int, alpha: Number)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param callback The function will be called every available frame. If `null` is passed it will stop any already ongoing animation.
|
||||||
|
*/
|
||||||
|
fun setAnimationLoop(callback: () -> Unit)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render a scene using a camera.
|
* Render a scene using a camera.
|
||||||
* The render is done to the renderTarget (if specified) or to the canvas as usual.
|
* The render is done to the renderTarget (if specified) or to the canvas as usual.
|
||||||
@ -94,7 +99,7 @@ external class WebGLRenderer(params: WebGLRendererParams = definedExternally) {
|
|||||||
scene: Scene,
|
scene: Scene,
|
||||||
camera: Camera,
|
camera: Camera,
|
||||||
renderTarget: dynamic = definedExternally,
|
renderTarget: dynamic = definedExternally,
|
||||||
forceClear: Boolean = definedExternally
|
forceClear: Boolean = definedExternally,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun setPixelRatio(value: Number)
|
fun setPixelRatio(value: Number)
|
||||||
|
@ -3,7 +3,7 @@ package hep.dataforge.vision.solid.fx
|
|||||||
import hep.dataforge.context.Context
|
import hep.dataforge.context.Context
|
||||||
import hep.dataforge.context.ContextAware
|
import hep.dataforge.context.ContextAware
|
||||||
import hep.dataforge.meta.empty
|
import hep.dataforge.meta.empty
|
||||||
import hep.dataforge.vision.Renderer
|
import hep.dataforge.vision.layout.Output
|
||||||
import hep.dataforge.vision.solid.Solid
|
import hep.dataforge.vision.solid.Solid
|
||||||
import hep.dataforge.vision.solid.specifications.Canvas3DOptions
|
import hep.dataforge.vision.solid.specifications.Canvas3DOptions
|
||||||
import javafx.application.Platform
|
import javafx.application.Platform
|
||||||
@ -15,7 +15,7 @@ import org.fxyz3d.scene.Axes
|
|||||||
import tornadofx.*
|
import tornadofx.*
|
||||||
|
|
||||||
class FXCanvas3D(val plugin: FX3DPlugin, val spec: Canvas3DOptions = Canvas3DOptions.empty()) :
|
class FXCanvas3D(val plugin: FX3DPlugin, val spec: Canvas3DOptions = Canvas3DOptions.empty()) :
|
||||||
Fragment(), Renderer<Solid>, ContextAware {
|
Fragment(), Output<Solid>, ContextAware {
|
||||||
|
|
||||||
override val context: Context get() = plugin.context
|
override val context: Context get() = plugin.context
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user