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

View File

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

View File

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

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

View File

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

View File

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

View File

@ -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)
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) {
if (type != Vision::class) error("Supports only DisplayObject")
@Suppress("UNCHECKED_CAST")
override fun output(name: Name, meta: Meta): Output<Solid> = outputs.getOrPut(name) {
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)
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
} as Renderer<T>
}
}

View File

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

View File

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

View File

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

View File

@ -54,7 +54,7 @@ public val ThreeControls: FunctionalComponent<ThreeControlsProps> = functionalCo
if (selectedObject != null) {
visionPropertyEditor(
selectedObject,
default = selectedObject.getAllProperties(),
default = selectedObject.allProperties,
key = selected
)
}

View File

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

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

View File

@ -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 {
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? = sequence {
yield(properties?.get(name))
yieldAll(getStyleItems(name))
yield(descriptor?.get(name)?.defaultItem())
if (inherit) {
yield(parent?.getProperty(name, inherit))
}.merge()
} else {
sequence {
yield(properties?.get(name))
yieldAll(getStyleItems(name))
}
yield(descriptor?.get(name)?.defaultItem())
}.merge()
}
}
/**
* Reset all properties to their default values

View File

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

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 fun getAllProperties(): Laminate = Laminate(Meta.EMPTY)
override val allProperties: Laminate
get() = Laminate(Meta.EMPTY)
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
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
}
///**
// * Start static server (updates via reload)
//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
// */
//@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)
// 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)
////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.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()

View File

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

View File

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

View File

@ -54,7 +54,6 @@ public class SolidMaterial : Scheme() {
NodeDescriptor {
value(COLOR_KEY) {
type(ValueType.STRING, ValueType.NUMBER)
default("#ffffff")
widgetType = "color"
}
value(OPACITY_KEY) {

View File

@ -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 {
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()
} else {
sequence {
yield(properties?.get(name))
yieldAll(getStyleItems(name))
yield(prototype.getProperty(name, false))
}.merge()
}
}
}.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

View File

@ -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
/**
* Attach canvas to given [HTMLElement]
* Force camera aspect ration and renderer size recalculation
*/
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
public fun updateSize() {
val width = canvas.clientWidth
val height = canvas.clientHeight
renderer.setSize(width, height, false)
camera.aspect = width.toDouble() / height.toDouble()
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", {
/**
* Attach canvas to given [HTMLElement]
*/
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()
}
/**

View File

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

View File

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

View File

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

View File

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