forked from kscience/visionforge
Fix a lot of bugs
This commit is contained in:
parent
6a48948c15
commit
cae3ab00d9
@ -1,6 +1,7 @@
|
||||
package hep.dataforge.vision.gdml.demo
|
||||
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.meta.invoke
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.vision.Vision
|
||||
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.solid.Solid
|
||||
import hep.dataforge.vision.solid.SolidManager
|
||||
import hep.dataforge.vision.solid.specifications.Canvas3DOptions
|
||||
import hep.dataforge.vision.solid.three.ThreeCanvas
|
||||
import kotlinx.browser.window
|
||||
import kotlinx.css.*
|
||||
@ -104,7 +106,9 @@ val GDMLApp = functionalComponent<GDMLAppProps>("GDMLApp") { props ->
|
||||
this.context = props.context
|
||||
this.obj = vision as? Solid
|
||||
this.selected = selected
|
||||
this.clickCallback = onSelect
|
||||
this.options = Canvas3DOptions.invoke{
|
||||
this.onSelect = onSelect
|
||||
}
|
||||
this.canvasCallback = {
|
||||
canvas = it
|
||||
}
|
||||
|
@ -1,12 +1,11 @@
|
||||
package hep.dataforge.vision.gdml.demo
|
||||
|
||||
import hep.dataforge.context.Global
|
||||
import hep.dataforge.vision.VisionManager
|
||||
import hep.dataforge.vision.editor.VisualObjectEditorFragment
|
||||
import hep.dataforge.vision.editor.VisualObjectTreeFragment
|
||||
import hep.dataforge.vision.gdml.toVision
|
||||
import hep.dataforge.vision.VisionManager
|
||||
import hep.dataforge.vision.solid.Solid
|
||||
import hep.dataforge.vision.solid.SolidManager
|
||||
import hep.dataforge.vision.solid.SolidMaterial
|
||||
import hep.dataforge.vision.solid.fx.FX3DPlugin
|
||||
import hep.dataforge.vision.solid.fx.FXCanvas3D
|
||||
@ -27,7 +26,7 @@ class GDMLView : View() {
|
||||
}
|
||||
|
||||
private val propertyEditor = VisualObjectEditorFragment {
|
||||
it.getAllProperties()
|
||||
it.allProperties
|
||||
}.apply {
|
||||
descriptorProperty.set(SolidMaterial.descriptor)
|
||||
itemProperty.bind(treeFragment.selectedProperty)
|
||||
|
@ -51,7 +51,7 @@ val MMApp = functionalComponent<MMAppProps>("Muon monitor") { props ->
|
||||
var selected by useState { props.selected }
|
||||
var canvas: ThreeCanvas? by useState { null }
|
||||
|
||||
val select: (Name?) -> Unit = {
|
||||
val onSelect: (Name?) -> Unit = {
|
||||
selected = it
|
||||
}
|
||||
|
||||
@ -72,7 +72,7 @@ val MMApp = functionalComponent<MMAppProps>("Muon monitor") { props ->
|
||||
}
|
||||
//tree
|
||||
card("Object tree") {
|
||||
objectTree(root, selected, select)
|
||||
objectTree(root, selected, onSelect)
|
||||
}
|
||||
}
|
||||
styledDiv {
|
||||
@ -85,9 +85,10 @@ val MMApp = functionalComponent<MMAppProps>("Muon monitor") { props ->
|
||||
attrs {
|
||||
this.context = props.context
|
||||
this.obj = root
|
||||
this.options = canvasConfig
|
||||
this.options = canvasConfig.apply {
|
||||
this.onSelect = onSelect
|
||||
}
|
||||
this.selected = selected
|
||||
this.clickCallback = select
|
||||
this.canvasCallback = {
|
||||
canvas = it
|
||||
}
|
||||
@ -176,7 +177,7 @@ val MMApp = functionalComponent<MMAppProps>("Muon monitor") { props ->
|
||||
configEditor(
|
||||
selectedObject.config,
|
||||
selectedObject.descriptor,
|
||||
default = selectedObject.getAllProperties(),
|
||||
default = selectedObject.allProperties,
|
||||
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 {
|
||||
id("ru.mipt.npm.mpp")
|
||||
@ -23,8 +25,13 @@ kotlin {
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api(project(":visionforge-solid"))
|
||||
api(project(":visionforge-gdml"))
|
||||
implementation(project(":visionforge-solid"))
|
||||
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.invoke
|
||||
import hep.dataforge.names.toName
|
||||
import hep.dataforge.output.OutputManager
|
||||
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.specifications.Canvas3DOptions
|
||||
import hep.dataforge.vision.visible
|
||||
@ -16,12 +15,12 @@ import kotlin.math.sin
|
||||
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 {
|
||||
"title" put title
|
||||
}
|
||||
val output = get(Vision::class, name.toName(), meta = meta)
|
||||
output.render (action = block)
|
||||
val output = output(name.toName(), meta)?: error("Output with name $name not found")
|
||||
output.solidGroup (builder = block)
|
||||
}
|
||||
|
||||
val canvasOptions = Canvas3DOptions {
|
||||
@ -36,7 +35,7 @@ val canvasOptions = Canvas3DOptions {
|
||||
}
|
||||
}
|
||||
|
||||
fun OutputManager.showcase() {
|
||||
fun Page<Solid>.showcase() {
|
||||
demo("shapes", "Basic shapes") {
|
||||
box(100.0, 100.0, 100.0) {
|
||||
z = -110.0
|
||||
@ -133,7 +132,7 @@ fun OutputManager.showcase() {
|
||||
}
|
||||
}
|
||||
|
||||
fun OutputManager.showcaseCSG() {
|
||||
fun Page<Solid>.showcaseCSG() {
|
||||
demo("CSG.simple", "CSG operations") {
|
||||
composite(CompositeType.UNION) {
|
||||
box(100, 100, 100) {
|
||||
|
@ -15,7 +15,7 @@ private class ThreeDemoApp : Application {
|
||||
|
||||
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 {
|
||||
showcase()
|
||||
|
@ -5,57 +5,75 @@ import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.meta.get
|
||||
import hep.dataforge.meta.string
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.output.Renderer
|
||||
import hep.dataforge.vision.Vision
|
||||
import hep.dataforge.vision.layout.Output
|
||||
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.ThreePlugin
|
||||
import hep.dataforge.vision.solid.three.attachRenderer
|
||||
import kotlinx.browser.document
|
||||
import kotlinx.dom.clear
|
||||
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.js.div
|
||||
import kotlinx.html.span
|
||||
import kotlinx.html.js.*
|
||||
import kotlinx.html.role
|
||||
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 three = Global.plugins.fetch(ThreePlugin)
|
||||
|
||||
init {
|
||||
element.clear()
|
||||
element.append(gridRoot)
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : Any> get(type: KClass<out T>, name: Name, stage: Name, meta: Meta): Renderer<T> {
|
||||
|
||||
return outputs.getOrPut(name) {
|
||||
if (type != Vision::class) error("Supports only DisplayObject")
|
||||
lateinit var output: ThreeCanvas
|
||||
//TODO calculate cell width here using jquery
|
||||
gridRoot.append {
|
||||
span("border") {
|
||||
div("col-6") {
|
||||
div { id = "output-$name" }.also {
|
||||
output = three.attachRenderer(it, canvasOptions)
|
||||
//output.attach(it)
|
||||
}
|
||||
hr()
|
||||
h2 { +(meta["title"].string ?: name.toString()) }
|
||||
}
|
||||
element.append {
|
||||
div("container") {
|
||||
navigationElement = ul("nav nav-tabs") {
|
||||
id = "${idPrefix}Tab"
|
||||
role = "tablist"
|
||||
}
|
||||
contentElement = div("tab-content") {
|
||||
id = "${idPrefix}TabContent"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output
|
||||
} as Renderer<T>
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun output(name: Name, meta: Meta): Output<Solid> = outputs.getOrPut(name) {
|
||||
lateinit var output: ThreeCanvas
|
||||
navigationElement.append {
|
||||
li("nav-item") {
|
||||
a(classes = "nav-link") {
|
||||
id = "tab[$name]"
|
||||
attributes["data-toggle"] = "tab"
|
||||
href = "#$name"
|
||||
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()
|
||||
h2 { +(meta["title"].string ?: name.toString()) }
|
||||
}
|
||||
}
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,7 @@ import hep.dataforge.vision.set
|
||||
import hep.dataforge.vision.setProperty
|
||||
import hep.dataforge.vision.solid.*
|
||||
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.ThreeMaterials.getMaterial
|
||||
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.EDGES_KEY) -> mesh.applyEdges(this@VariableBox)
|
||||
name.startsWith(MATERIAL_COLOR_KEY)->{
|
||||
mesh.material = getMaterial(this, true)
|
||||
}
|
||||
else -> mesh.updateProperty(this@VariableBox, name)
|
||||
}
|
||||
}
|
||||
|
@ -4,14 +4,25 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<title>Three js demo for particle physics</title>
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
|
||||
integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
|
||||
<!-- CSS -->
|
||||
<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>
|
||||
</head>
|
||||
<body class="application">
|
||||
<div class="container">
|
||||
<h1>Demo grid</h1>
|
||||
</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>
|
||||
</html>
|
@ -3,18 +3,17 @@ package hep.dataforge.vision.solid.demo
|
||||
import hep.dataforge.context.Global
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.output.OutputManager
|
||||
import hep.dataforge.output.Renderer
|
||||
import hep.dataforge.vision.Vision
|
||||
import hep.dataforge.vision.layout.Output
|
||||
import hep.dataforge.vision.layout.Page
|
||||
import hep.dataforge.vision.solid.Solid
|
||||
import hep.dataforge.vision.solid.fx.FX3DPlugin
|
||||
import hep.dataforge.vision.solid.fx.FXCanvas3D
|
||||
import javafx.collections.FXCollections
|
||||
import javafx.scene.Parent
|
||||
import javafx.scene.control.Tab
|
||||
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>()
|
||||
|
||||
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)
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : Any> get(type: KClass<out T>, name: Name, stage: Name, meta: Meta): Renderer<T> {
|
||||
return outputs.getOrPut(name) {
|
||||
if (type != Vision::class) kotlin.error("Supports only DisplayObject")
|
||||
val output = FXCanvas3D(fx3d, canvasOptions)
|
||||
|
||||
output
|
||||
} as Renderer<T>
|
||||
override fun output(name: Name, meta: Meta): Output<Solid> = outputs.getOrPut(name) {
|
||||
FXCanvas3D(fx3d, canvasOptions)
|
||||
}
|
||||
|
||||
}
|
@ -54,7 +54,7 @@ public val ThreeControls: FunctionalComponent<ThreeControlsProps> = functionalCo
|
||||
if (selectedObject != null) {
|
||||
visionPropertyEditor(
|
||||
selectedObject,
|
||||
default = selectedObject.getAllProperties(),
|
||||
default = selectedObject.allProperties,
|
||||
key = selected
|
||||
)
|
||||
}
|
||||
|
@ -19,7 +19,6 @@ public external interface ThreeCanvasProps : RProps {
|
||||
public var obj: Solid?
|
||||
public var options: Canvas3DOptions?
|
||||
public var selected: Name?
|
||||
public var clickCallback: (Name?) -> 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 three: ThreePlugin = props.context.plugins.fetch(ThreePlugin)
|
||||
val newCanvas: ThreeCanvas =
|
||||
three.attachRenderer(element, props.options ?: Canvas3DOptions.empty(), props.clickCallback)
|
||||
three.createCanvas(element, props.options ?: Canvas3DOptions.empty())
|
||||
props.canvasCallback?.invoke(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 kotlinx.serialization.PolymorphicSerializer
|
||||
import kotlinx.serialization.Transient
|
||||
import kotlin.properties.ReadWriteProperty
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public fun getAllProperties(): Laminate
|
||||
public val allProperties: Laminate
|
||||
|
||||
/**
|
||||
* Get property (including styles). [inherit] toggles parent node property lookup
|
||||
@ -116,27 +114,27 @@ public var Vision.visible: Boolean?
|
||||
get() = getProperty(VISIBLE_KEY).boolean
|
||||
set(value) = config.setValue(VISIBLE_KEY, value?.asValue())
|
||||
|
||||
/**
|
||||
* Convinience delegate for properties
|
||||
*/
|
||||
public fun Vision.property(
|
||||
default: MetaItem<*>? = null,
|
||||
key: Name? = null,
|
||||
inherit: Boolean = true,
|
||||
): MutableItemDelegate =
|
||||
object : ReadWriteProperty<Any?, MetaItem<*>?> {
|
||||
override fun getValue(thisRef: Any?, property: KProperty<*>): MetaItem<*>? {
|
||||
val name = key ?: property.name.toName()
|
||||
return getProperty(name, inherit) ?: default
|
||||
}
|
||||
///**
|
||||
// * Convenience delegate for properties
|
||||
// */
|
||||
//public fun Vision.property(
|
||||
// default: MetaItem<*>? = null,
|
||||
// key: Name? = null,
|
||||
// inherit: Boolean = true,
|
||||
//): MutableItemDelegate =
|
||||
// object : ReadWriteProperty<Any?, MetaItem<*>?> {
|
||||
// override fun getValue(thisRef: Any?, property: KProperty<*>): MetaItem<*>? {
|
||||
// val name = key ?: property.name.toName()
|
||||
// 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<*>?) {
|
||||
val name = key ?: property.name.toName()
|
||||
setProperty(name, value)
|
||||
}
|
||||
}
|
||||
|
||||
public fun Vision.properties(inherit: Boolean = true): MutableItemProvider = object : MutableItemProvider {
|
||||
public fun Vision.props(inherit: Boolean = true): MutableItemProvider = object : MutableItemProvider {
|
||||
override fun getItem(name: Name): MetaItem<*>? {
|
||||
return getProperty(name, inherit)
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package hep.dataforge.vision
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.meta.descriptors.NodeDescriptor
|
||||
import hep.dataforge.meta.descriptors.defaultItem
|
||||
import hep.dataforge.meta.descriptors.defaultMeta
|
||||
import hep.dataforge.meta.descriptors.get
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.asName
|
||||
@ -75,24 +76,22 @@ public open class VisionBase : Vision {
|
||||
/**
|
||||
* 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<*>? {
|
||||
return if (inherit) {
|
||||
sequence {
|
||||
yield(properties?.get(name))
|
||||
yieldAll(getStyleItems(name))
|
||||
yield(descriptor?.get(name)?.defaultItem())
|
||||
yield(parent?.getProperty(name, inherit))
|
||||
}.merge()
|
||||
} else {
|
||||
sequence {
|
||||
yield(properties?.get(name))
|
||||
yieldAll(getStyleItems(name))
|
||||
yield(descriptor?.get(name)?.defaultItem())
|
||||
}.merge()
|
||||
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? = sequence {
|
||||
yield(properties?.get(name))
|
||||
yieldAll(getStyleItems(name))
|
||||
if (inherit) {
|
||||
yield(parent?.getProperty(name, inherit))
|
||||
}
|
||||
}
|
||||
yield(descriptor?.get(name)?.defaultItem())
|
||||
}.merge()
|
||||
|
||||
/**
|
||||
* Reset all properties to their default values
|
||||
|
@ -19,17 +19,13 @@ public class VisionManager(meta: Meta) : AbstractPlugin(meta) {
|
||||
public val serializersModule: SerializersModule
|
||||
get() = SerializersModule {
|
||||
include(defaultSerialModule)
|
||||
context.gather<SerializersModule>(VISION_SERIAL_MODULE_TARGET).values.forEach {
|
||||
context.gather<SerializersModule>(VISION_SERIALIZER_MODULE_TARGET).values.forEach {
|
||||
include(it)
|
||||
}
|
||||
}
|
||||
|
||||
public val jsonFormat: Json
|
||||
get() = Json {
|
||||
prettyPrint = true
|
||||
useArrayPolymorphism = false
|
||||
encodeDefaults = false
|
||||
ignoreUnknownKeys = true
|
||||
get() = Json(defaultJson) {
|
||||
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 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)
|
||||
|
||||
private val defaultSerialModule: SerializersModule = SerializersModule {
|
||||
polymorphic(Vision::class) {
|
||||
subclass(VisionBase.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 fun getAllProperties(): Laminate = Laminate(Meta.EMPTY)
|
||||
override val allProperties: Laminate
|
||||
get() = Laminate(Meta.EMPTY)
|
||||
|
||||
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
|
||||
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.toName
|
||||
import hep.dataforge.vision.server.VisionServer.Companion.DEFAULT_PAGE
|
||||
import io.ktor.application.Application
|
||||
import io.ktor.application.featureOrNull
|
||||
import io.ktor.application.install
|
||||
import io.ktor.features.CORS
|
||||
import io.ktor.http.content.resources
|
||||
import io.ktor.http.content.static
|
||||
import io.ktor.routing.Routing
|
||||
import io.ktor.routing.route
|
||||
import io.ktor.routing.routing
|
||||
import io.ktor.server.engine.ApplicationEngine
|
||||
import io.ktor.websocket.WebSockets
|
||||
import kotlinx.html.TagConsumer
|
||||
import java.awt.Desktop
|
||||
import java.net.URI
|
||||
import kotlin.text.get
|
||||
|
||||
public enum class ServerUpdateMode {
|
||||
NONE,
|
||||
PUSH,
|
||||
PULL
|
||||
}
|
||||
|
||||
public class VisionServer internal constructor(
|
||||
private val routing: Routing,
|
||||
private val rootRoute: String,
|
||||
) : Configurable {
|
||||
override val config: Config = Config()
|
||||
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 embedData: Boolean by config.boolean(false)
|
||||
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
|
||||
//package hep.dataforge.vision.server
|
||||
//
|
||||
//import hep.dataforge.meta.*
|
||||
//import hep.dataforge.names.Name
|
||||
//import hep.dataforge.names.toName
|
||||
//import hep.dataforge.vision.server.VisionServer.Companion.DEFAULT_PAGE
|
||||
//import io.ktor.application.Application
|
||||
//import io.ktor.application.featureOrNull
|
||||
//import io.ktor.application.install
|
||||
//import io.ktor.features.CORS
|
||||
//import io.ktor.http.content.resources
|
||||
//import io.ktor.http.content.static
|
||||
//import io.ktor.routing.Routing
|
||||
//import io.ktor.routing.route
|
||||
//import io.ktor.routing.routing
|
||||
//import io.ktor.server.engine.ApplicationEngine
|
||||
//import io.ktor.websocket.WebSockets
|
||||
//import kotlinx.html.TagConsumer
|
||||
//import java.awt.Desktop
|
||||
//import java.net.URI
|
||||
//import kotlin.text.get
|
||||
//
|
||||
//public enum class ServerUpdateMode {
|
||||
// NONE,
|
||||
// PUSH,
|
||||
// PULL
|
||||
//}
|
||||
//
|
||||
//public class VisionServer internal constructor(
|
||||
// private val routing: Routing,
|
||||
// private val rootRoute: String,
|
||||
//) : Configurable {
|
||||
// override val config: Config = Config()
|
||||
// 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 embedData: Boolean by config.boolean(false)
|
||||
//
|
||||
// /**
|
||||
// * 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()
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//
|
||||
///**
|
||||
// * Start static server (updates via reload)
|
||||
// * Attach plotly application to given server
|
||||
// */
|
||||
//@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)
|
||||
//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.plus
|
||||
import hep.dataforge.vision.VisionContainerBuilder
|
||||
import hep.dataforge.vision.properties
|
||||
import hep.dataforge.vision.props
|
||||
import hep.dataforge.vision.set
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
@ -18,7 +18,7 @@ import kotlinx.serialization.UseSerializers
|
||||
public class PolyLine(public var points: List<Point3D>) : BasicSolid(), Solid {
|
||||
|
||||
//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 val THICKNESS_KEY: Name = "thickness".asName()
|
||||
|
@ -7,10 +7,10 @@ import hep.dataforge.names.asName
|
||||
import hep.dataforge.names.plus
|
||||
import hep.dataforge.values.ValueType
|
||||
import hep.dataforge.values.asValue
|
||||
import hep.dataforge.vision.Renderer
|
||||
import hep.dataforge.vision.Vision
|
||||
import hep.dataforge.vision.Vision.Companion.VISIBLE_KEY
|
||||
import hep.dataforge.vision.enum
|
||||
import hep.dataforge.vision.layout.Output
|
||||
import hep.dataforge.vision.setProperty
|
||||
import hep.dataforge.vision.solid.Solid.Companion.DETAIL_KEY
|
||||
import hep.dataforge.vision.solid.Solid.Companion.IGNORE_KEY
|
||||
@ -104,7 +104,7 @@ public var Solid.layer: Int
|
||||
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
|
||||
|
||||
|
@ -11,7 +11,7 @@ import hep.dataforge.vision.Vision
|
||||
import hep.dataforge.vision.VisionGroup
|
||||
import hep.dataforge.vision.VisionGroupBase
|
||||
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.modules.*
|
||||
import kotlin.reflect.KClass
|
||||
@ -24,7 +24,7 @@ public class SolidManager(meta: Meta) : AbstractPlugin(meta) {
|
||||
override val tag: PluginTag get() = Companion.tag
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@ -60,11 +60,7 @@ public class SolidManager(meta: Meta) : AbstractPlugin(meta) {
|
||||
}
|
||||
}
|
||||
|
||||
internal val jsonForSolids: Json = Json{
|
||||
prettyPrint = true
|
||||
useArrayPolymorphism = false
|
||||
encodeDefaults = false
|
||||
ignoreUnknownKeys = true
|
||||
internal val jsonForSolids: Json = Json(VisionManager.defaultJson){
|
||||
serializersModule = serializersModuleForSolids
|
||||
}
|
||||
|
||||
|
@ -54,7 +54,6 @@ public class SolidMaterial : Scheme() {
|
||||
NodeDescriptor {
|
||||
value(COLOR_KEY) {
|
||||
type(ValueType.STRING, ValueType.NUMBER)
|
||||
default("#ffffff")
|
||||
widgetType = "color"
|
||||
}
|
||||
value(OPACITY_KEY) {
|
||||
|
@ -12,22 +12,14 @@ import kotlin.collections.set
|
||||
public abstract class AbstractReference : BasicSolid(), VisionGroup {
|
||||
public abstract val prototype: Solid
|
||||
|
||||
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? {
|
||||
return if (inherit) {
|
||||
sequence {
|
||||
yield(properties?.get(name))
|
||||
yieldAll(getStyleItems(name))
|
||||
yield(prototype.getProperty(name))
|
||||
yield(parent?.getProperty(name, inherit))
|
||||
}.merge()
|
||||
} else {
|
||||
sequence {
|
||||
yield(properties?.get(name))
|
||||
yieldAll(getStyleItems(name))
|
||||
yield(prototype.getProperty(name, false))
|
||||
}.merge()
|
||||
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? = sequence {
|
||||
yield(properties?.get(name))
|
||||
yieldAll(getStyleItems(name))
|
||||
yield(prototype.getProperty(name))
|
||||
if (inherit) {
|
||||
yield(parent?.getProperty(name, inherit))
|
||||
}
|
||||
}
|
||||
}.merge()
|
||||
|
||||
override var styles: List<String>
|
||||
get() = (properties[Vision.STYLE_KEY]?.stringList ?: emptyList()) + prototype.styles
|
||||
@ -35,8 +27,13 @@ public abstract class AbstractReference : BasicSolid(), VisionGroup {
|
||||
config[Vision.STYLE_KEY] = value
|
||||
}
|
||||
|
||||
override fun getAllProperties(): Laminate =
|
||||
Laminate(properties, allStyles, prototype.getAllProperties(), parent?.getAllProperties())
|
||||
override val allProperties: Laminate
|
||||
get() = Laminate(
|
||||
properties,
|
||||
allStyles,
|
||||
prototype.allProperties,
|
||||
parent?.allProperties,
|
||||
)
|
||||
|
||||
override fun attachChildren() {
|
||||
//do nothing
|
||||
|
@ -6,7 +6,7 @@ import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.plus
|
||||
import hep.dataforge.names.toName
|
||||
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.specifications.*
|
||||
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.objects.LineSegments
|
||||
import info.laht.threekt.objects.Mesh
|
||||
import info.laht.threekt.renderers.WebGLRenderer
|
||||
import info.laht.threekt.scenes.Scene
|
||||
import kotlinx.browser.window
|
||||
import kotlinx.dom.clear
|
||||
import org.w3c.dom.Element
|
||||
import org.w3c.dom.HTMLCanvasElement
|
||||
import org.w3c.dom.HTMLElement
|
||||
import org.w3c.dom.Node
|
||||
import org.w3c.dom.events.MouseEvent
|
||||
import kotlin.math.cos
|
||||
@ -41,7 +38,7 @@ import kotlin.math.sin
|
||||
public class ThreeCanvas(
|
||||
public val three: ThreePlugin,
|
||||
public val options: Canvas3DOptions,
|
||||
) : Renderer<Solid> {
|
||||
) : Output<Solid> {
|
||||
private var root: Object3D? = null
|
||||
|
||||
private val raycaster = Raycaster()
|
||||
@ -62,55 +59,60 @@ public class ThreeCanvas(
|
||||
|
||||
private var picked: Object3D? = null
|
||||
|
||||
private val renderer = WebGLRenderer { antialias = true }.apply {
|
||||
setClearColor(Colors.skyblue, 1)
|
||||
}
|
||||
|
||||
public val canvas: HTMLCanvasElement = renderer.domElement as HTMLCanvasElement
|
||||
|
||||
/**
|
||||
* Force camera aspect ration and renderer size recalculation
|
||||
*/
|
||||
public fun updateSize() {
|
||||
val width = canvas.clientWidth
|
||||
val height = canvas.clientHeight
|
||||
renderer.setSize(width, height, false)
|
||||
camera.aspect = width.toDouble() / height.toDouble()
|
||||
camera.updateProjectionMatrix()
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach canvas to given [HTMLElement]
|
||||
*/
|
||||
public fun attach(element: HTMLElement) {
|
||||
fun WebGLRenderer.resize() {
|
||||
val canvas = domElement as HTMLCanvasElement
|
||||
|
||||
val width = options.computeWidth(canvas.clientWidth)
|
||||
val height = options.computeHeight(canvas.clientHeight)
|
||||
|
||||
canvas.width = width
|
||||
canvas.height = height
|
||||
|
||||
setSize(width, height, false)
|
||||
camera.aspect = width.toDouble() / height
|
||||
camera.updateProjectionMatrix()
|
||||
}
|
||||
|
||||
element.clear()
|
||||
|
||||
//Attach listener to track mouse changes
|
||||
element.addEventListener("mousemove", { event ->
|
||||
(event as? MouseEvent)?.run {
|
||||
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", {
|
||||
init {
|
||||
canvas.addEventListener("pointerdown", {
|
||||
val picked = pick()
|
||||
options.onSelect?.invoke(picked?.fullName())
|
||||
}, false)
|
||||
|
||||
val renderer = WebGLRenderer { antialias = true }.apply {
|
||||
setClearColor(Colors.skyblue, 1)
|
||||
}
|
||||
//Attach listener to track mouse changes
|
||||
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 {
|
||||
width = "100%"
|
||||
minWidth = "${options.minWith.toInt()}px"
|
||||
maxWidth = "${options.maxWith.toInt()}px"
|
||||
height = "100%"
|
||||
minHeight = "${options.minHeight.toInt()}px"
|
||||
maxHeight = "${options.maxHeight.toInt()}px"
|
||||
display = "block"
|
||||
}
|
||||
|
||||
addControls(renderer.domElement, options.controls)
|
||||
|
||||
fun animate() {
|
||||
canvas.onresize = {
|
||||
updateSize()
|
||||
}
|
||||
|
||||
addControls(canvas, options.controls)
|
||||
|
||||
renderer.setAnimationLoop {
|
||||
val picked = pick()
|
||||
|
||||
if (picked != null && this.picked != picked) {
|
||||
@ -119,21 +121,13 @@ public class ThreeCanvas(
|
||||
this.picked = picked
|
||||
}
|
||||
|
||||
window.requestAnimationFrame {
|
||||
animate()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
fun getLineMaterial(meta: Meta?, cache: Boolean): LineBasicMaterial {
|
||||
public fun getLineMaterial(meta: Meta?, cache: Boolean): LineBasicMaterial {
|
||||
if (meta == null) return DEFAULT_LINE
|
||||
return if (cache) {
|
||||
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
|
||||
return if (cache) {
|
||||
materialCache.getOrPut(meta) { buildMaterial(meta) }
|
||||
@ -87,7 +87,7 @@ public object ThreeMaterials {
|
||||
/**
|
||||
* Infer color based on meta item
|
||||
*/
|
||||
fun MetaItem<*>.getColor(): Color {
|
||||
public fun MetaItem<*>.getColor(): Color {
|
||||
return when (this) {
|
||||
is MetaItem.ValueItem -> if (this.value.type == ValueType.NUMBER) {
|
||||
val int = value.number.toInt()
|
||||
|
@ -6,17 +6,18 @@ import hep.dataforge.meta.empty
|
||||
import hep.dataforge.meta.invoke
|
||||
import hep.dataforge.names.*
|
||||
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.specifications.Canvas3DOptions
|
||||
import hep.dataforge.vision.visible
|
||||
import info.laht.threekt.core.Object3D
|
||||
import org.w3c.dom.Element
|
||||
import org.w3c.dom.HTMLElement
|
||||
import kotlin.collections.set
|
||||
import kotlin.reflect.KClass
|
||||
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
|
||||
|
||||
public val solidManager: SolidManager by require(SolidManager)
|
||||
@ -115,10 +116,15 @@ public class ThreePlugin : AbstractPlugin(), HTMLVisionDisplay<Solid, ThreeCanva
|
||||
}
|
||||
}
|
||||
|
||||
override fun attachRenderer(element: HTMLElement): ThreeCanvas {
|
||||
return ThreeCanvas(this, Canvas3DOptions.empty()).apply {
|
||||
attach(element)
|
||||
}
|
||||
public fun createCanvas(
|
||||
element: HTMLElement,
|
||||
options: Canvas3DOptions = Canvas3DOptions.empty(),
|
||||
): ThreeCanvas = ThreeCanvas(this, options).apply {
|
||||
attach(element)
|
||||
}
|
||||
|
||||
override fun bind(element: Element, vision: Solid) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
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(
|
||||
element: HTMLElement,
|
||||
obj: Solid,
|
||||
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) {
|
||||
object3D.name = token.toString()
|
||||
|
@ -64,7 +64,7 @@ external class WebGLRenderer(params: WebGLRendererParams = definedExternally) {
|
||||
fun clear(
|
||||
color: Boolean = definedExternally,
|
||||
depth: Boolean = definedExternally,
|
||||
stencil: Boolean = definedExternally
|
||||
stencil: Boolean = definedExternally,
|
||||
)
|
||||
|
||||
fun clearColor()
|
||||
@ -84,6 +84,11 @@ external class WebGLRenderer(params: WebGLRendererParams = definedExternally) {
|
||||
*/
|
||||
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.
|
||||
* 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,
|
||||
camera: Camera,
|
||||
renderTarget: dynamic = definedExternally,
|
||||
forceClear: Boolean = definedExternally
|
||||
forceClear: Boolean = definedExternally,
|
||||
)
|
||||
|
||||
fun setPixelRatio(value: Number)
|
||||
|
@ -3,7 +3,7 @@ package hep.dataforge.vision.solid.fx
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.context.ContextAware
|
||||
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.specifications.Canvas3DOptions
|
||||
import javafx.application.Platform
|
||||
@ -15,7 +15,7 @@ import org.fxyz3d.scene.Axes
|
||||
import tornadofx.*
|
||||
|
||||
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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user