Fix a lot of bugs

This commit is contained in:
Alexander Nozik 2020-11-19 13:43:42 +03:00
parent 6a48948c15
commit cae3ab00d9
37 changed files with 671 additions and 531 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +0,0 @@
package hep.dataforge.vision
public fun interface Renderer<in V: Vision> {
public fun render(vision: V)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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()
} }
/** /**

View File

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

View File

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

View File

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

View File

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