Merge pull request #32 from mipt-npm/dev
Major code refactoring
0
CHANGELOG.md
Normal file
89
README.md
@ -2,19 +2,19 @@
|
||||
|
||||
# DataForge Visualization Platform
|
||||
|
||||
## Table of contents
|
||||
## Table of Contents
|
||||
|
||||
* [Introduction](#introduction)
|
||||
* [Features](#features)
|
||||
* [About DataForge](#about-dataforge)
|
||||
* [Modules contained in this repository](#modules-contained-in-this-repository)
|
||||
* [dataforge-vis-common](#dataforge-vis-common)
|
||||
* [dataforge-vis-spatial](#dataforge-vis-spatial)
|
||||
* [dataforge-vis-spatial-gdml](#dataforge-vis-spatial-gdml)
|
||||
* [dataforge-vis-jsroot](#dataforge-vis-jsroot)
|
||||
* [visionforge-core](#visionforge-core)
|
||||
* [visionforge-solid](#visionforge-solid)
|
||||
* [visionforge-gdml](#visionforge-gdml)
|
||||
* [Visualization for External Systems](#visualization-for-external-systems)
|
||||
* [Demonstrations](#demonstrations)
|
||||
* [Spatial Showcase](#spatial-showcase)
|
||||
* [Muon Monitor](#muon-monitor-visualization)
|
||||
* [Simple Example - Spatial Showcase](#simple-example---spatial-showcase)
|
||||
* [Full-Stack Application Example - Muon Monitor](#full-stack-application-example---muon-monitor-visualization)
|
||||
* [GDML Example](#gdml-example)
|
||||
|
||||
|
||||
@ -26,8 +26,8 @@ used for visualization in various scientific applications.
|
||||
The main framework's use case for now is 3D visualization for particle physics experiments.
|
||||
Other applications including 2D plots are planned for the future.
|
||||
|
||||
The project is being developed as a Kotlin multiplatform application, currently targeting browser
|
||||
JavaScript and JVM.
|
||||
The project is developed as a [Kotlin multiplatform](https://kotlinlang.org/docs/reference/multiplatform.html)
|
||||
application, currently targeting browser JavaScript and JVM.
|
||||
|
||||
|
||||
## Features
|
||||
@ -41,7 +41,6 @@ The main framework's features for now include:
|
||||
- Settings export and import
|
||||
- Multiple platform support
|
||||
|
||||
|
||||
## About DataForge
|
||||
|
||||
DataForge is a software framework for automated scientific data processing. DataForge Visualization
|
||||
@ -56,25 +55,25 @@ To learn more about DataForge, please consult the following URLs:
|
||||
|
||||
## Modules contained in this repository
|
||||
|
||||
### dataforge-vis-common
|
||||
### visionforge-core
|
||||
|
||||
Contains a general hierarchy of classes and interfaces useful for visualization.
|
||||
This module is not specific to 3D-visualization.
|
||||
|
||||
The `dataforge-vis-common` module also includes configuration editors for JS (in `jsMain`) and JVM (in `jvmMain`).
|
||||
The `visionforge-core` module also includes configuration editors for JS (in `jsMain`) and JVM (in `jvmMain`).
|
||||
|
||||
##### Class diagram:
|
||||
**Class diagram:**
|
||||
|
||||
![](doc/resources/class-diag-common.png)
|
||||
![](doc/resources/class-diag-core.png)
|
||||
|
||||
|
||||
### dataforge-vis-spatial
|
||||
### visionforge-solid
|
||||
|
||||
Includes common classes and serializers for 3D visualization, Three.js and JavaFX implementations.
|
||||
Includes common classes and serializers for 3D visualization, as well as Three.js and JavaFX implementations.
|
||||
|
||||
##### Class diagram:
|
||||
**Class diagram:**
|
||||
|
||||
![](doc/resources/class-diag-3d.png)
|
||||
![](doc/resources/class-diag-solid.png)
|
||||
|
||||
##### Prototypes
|
||||
|
||||
@ -82,65 +81,63 @@ One of the important features of the framework is support for 3D object prototyp
|
||||
also referred to as templates). The idea is that prototype geometry can be rendered once and reused
|
||||
for multiple objects. This helps to significantly decrease memory usage.
|
||||
|
||||
The `prototypes` property tree is defined in `VisualGroup3D` class, and `Proxy` class helps to reuse a template object.
|
||||
The `prototypes` property tree is defined in `SolidGroup` class via `PrototypeHolder` interface, and
|
||||
`Proxy` class helps to reuse a template object.
|
||||
|
||||
##### Styles
|
||||
|
||||
`VisualGroup3D` has a `styleSheet` property that can optionally define styles at the Group's
|
||||
level. Styles are applied to child (descendant) objects using `styles: List<String>` property defined
|
||||
in `VisualObject`.
|
||||
`SolidGroup` has a `styleSheet` property that can optionally define styles at the Group's
|
||||
level. Styles are applied to child (descendant) objects using `Vision.styles: List<String>` property.
|
||||
|
||||
|
||||
### dataforge-vis-spatial-gdml
|
||||
### visionforge-gdml
|
||||
|
||||
GDML bindings for 3D visualization (to be moved to gdml project).
|
||||
|
||||
|
||||
### dataforge-vis-jsroot
|
||||
|
||||
Some JSROOT bindings.
|
||||
|
||||
Note: Currently, this part is experimental and put here for completeness. This module may not build.
|
||||
## Visualization for External Systems
|
||||
|
||||
The `visionforge` framework can be used to visualize geometry and events from external,
|
||||
non-Kotlin based systems, such as ROOT. This will require a plugin to convert data model
|
||||
of the external system to that of `visionforge`. Performing such integration is a work
|
||||
currently in progress.
|
||||
|
||||
|
||||
## Demonstrations
|
||||
|
||||
The `demo` module contains several demonstrations of using the `dataforge-vis` framework:
|
||||
The `demo` module contains several example projects (demonstrations) of using the `visionforge` framework.
|
||||
They are briefly described in this section, for more details please consult the corresponding per-project
|
||||
README file.
|
||||
|
||||
### Spatial Showcase
|
||||
### Simple Example - Spatial Showcase
|
||||
|
||||
Contains a simple demonstration with a grid including a few shapes that you can rotate, move camera, and so on.
|
||||
Some shapes will also periodically change their color and visibility.
|
||||
|
||||
To see the demo: run `demo/spatial-showcase/Tasks/distribution/installJsDist` Gradle task, then open
|
||||
`build/distribuions/spatial-showcase-js-0.1.0-dev/index.html` file in your browser.
|
||||
Some shapes will also periodically change their color and visibility.
|
||||
|
||||
##### Example view:
|
||||
[More details](demo/spatial-showcase/README.md)
|
||||
|
||||
**Example view:**
|
||||
|
||||
![](doc/resources/spatial-showcase.png)
|
||||
|
||||
### Muon Monitor Visualization
|
||||
|
||||
### Full-Stack Application Example - Muon Monitor Visualization
|
||||
|
||||
A full-stack application example, showing the
|
||||
[Muon Monitor](http://npm.mipt.ru/projects/physics.html#mounMonitor) experiment set-up.
|
||||
[Muon Monitor](http://npm.mipt.ru/en/projects/physics#mounMonitor) experiment set-up.
|
||||
|
||||
Includes server back-end generating events, as well as visualization front-end.
|
||||
[More details](demo/muon-monitor/README.md)
|
||||
|
||||
To run full-stack app (both server and browser front-end), run
|
||||
`demo/muon-monitor/application/run` task.
|
||||
|
||||
##### Example view:
|
||||
**Example view:**
|
||||
|
||||
![](doc/resources/muon-monitor.png)
|
||||
|
||||
|
||||
### GDML Example
|
||||
|
||||
Visualization example for geometry defined as GDML file.
|
||||
|
||||
To build the app, run `demo/gdml/Tasks/distribution/installJsDist` task, then open
|
||||
`build/distribuions/gdml-js-0.1.0-dev/index.html` file in your browser, and
|
||||
drag-and-drop GDML file to the window to see visualization. For an example file, use
|
||||
`demo/gdml/src/jsMain/resources/cubes.gdml`.
|
||||
[More details](demo/gdml/README.md)
|
||||
|
||||
##### Example view:
|
||||
|
||||
|
@ -1,15 +1,14 @@
|
||||
import scientifik.fx
|
||||
import scientifik.serialization
|
||||
import scientifik.useFx
|
||||
import scientifik.useSerialization
|
||||
|
||||
val dataforgeVersion by extra("0.1.7")
|
||||
val dataforgeVersion by extra("0.1.8")
|
||||
|
||||
plugins {
|
||||
val toolsVersion = "0.4.2"
|
||||
id("scientifik.mpp") version toolsVersion apply false
|
||||
id("scientifik.jvm") version toolsVersion apply false
|
||||
id("scientifik.js") version toolsVersion apply false
|
||||
id("scientifik.publish") version toolsVersion apply false
|
||||
id("org.openjfx.javafxplugin") version "0.0.8" apply false
|
||||
id("scientifik.mpp") apply false
|
||||
id("scientifik.jvm") apply false
|
||||
id("scientifik.js") apply false
|
||||
id("scientifik.publish") apply false
|
||||
id("org.jetbrains.changelog") version "0.4.0"
|
||||
}
|
||||
|
||||
allprojects {
|
||||
@ -17,24 +16,20 @@ allprojects {
|
||||
mavenLocal()
|
||||
maven("https://dl.bintray.com/pdvrieze/maven")
|
||||
maven("http://maven.jzy3d.org/releases")
|
||||
maven("https://kotlin.bintray.com/js-externals")
|
||||
maven("https://kotlin.bintray.com/kotlin-js-wrappers/")
|
||||
// maven("https://dl.bintray.com/gbaldeck/kotlin")
|
||||
// maven("https://dl.bintray.com/rjaros/kotlin")
|
||||
}
|
||||
|
||||
group = "hep.dataforge"
|
||||
version = "0.1.3-dev"
|
||||
version = "0.1.5-dev"
|
||||
}
|
||||
|
||||
val githubProject by extra("dataforge-vis")
|
||||
val githubProject by extra("visionforge")
|
||||
val bintrayRepo by extra("dataforge")
|
||||
val fxVersion by extra("14")
|
||||
|
||||
subprojects {
|
||||
apply(plugin = "scientifik.publish")
|
||||
serialization()
|
||||
afterEvaluate {
|
||||
fx(scientifik.FXModule.CONTROLS, version = fxVersion)
|
||||
if(name.startsWith("visionforge")) {
|
||||
apply(plugin = "scientifik.publish")
|
||||
}
|
||||
useSerialization()
|
||||
useFx(scientifik.FXModule.CONTROLS, version = fxVersion)
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
plugins {
|
||||
id("scientifik.mpp")
|
||||
}
|
||||
|
||||
val dataforgeVersion: String by rootProject.extra
|
||||
//val kvisionVersion: String by rootProject.extra("2.0.0-M1")
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api("hep.dataforge:dataforge-output:$dataforgeVersion")
|
||||
}
|
||||
}
|
||||
jvmMain {
|
||||
dependencies {
|
||||
api("no.tornado:tornadofx:1.7.20")
|
||||
//api("no.tornado:tornadofx-controlsfx:0.1.1")
|
||||
api("de.jensd:fontawesomefx-fontawesome:4.7.0-11") {
|
||||
exclude(group = "org.openjfx")
|
||||
}
|
||||
api("de.jensd:fontawesomefx-commons:11.0") {
|
||||
exclude(group = "org.openjfx")
|
||||
}
|
||||
}
|
||||
}
|
||||
jsMain {
|
||||
dependencies {
|
||||
api("hep.dataforge:dataforge-output-html:$dataforgeVersion")
|
||||
|
||||
//React, React DOM + Wrappers (chapter 3)
|
||||
api("org.jetbrains:kotlin-react:16.13.0-pre.94-kotlin-1.3.70")
|
||||
api("org.jetbrains:kotlin-react-dom:16.13.0-pre.94-kotlin-1.3.70")
|
||||
api(npm("react", "16.13.0"))
|
||||
api(npm("react-dom", "16.13.0"))
|
||||
|
||||
//Kotlin Styled (chapter 3)
|
||||
api("org.jetbrains:kotlin-styled:1.0.0-pre.94-kotlin-1.3.70")
|
||||
api(npm("styled-components"))
|
||||
api(npm("inline-style-prefixer"))
|
||||
|
||||
api(npm("source-map-resolve","0.6.0"))
|
||||
api(npm("bootstrap","4.3.1"))
|
||||
api(npm("popper.js","1.14.7"))
|
||||
api(npm("jquery","3.5.0"))
|
||||
//api(npm("jsoneditor", "8.6.1"))
|
||||
api(npm("file-saver","2.0.2"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,97 +0,0 @@
|
||||
package hep.dataforge.vis
|
||||
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.values.Value
|
||||
import hep.dataforge.vis.VisualObject.Companion.STYLE_KEY
|
||||
import kotlinx.serialization.Transient
|
||||
|
||||
internal data class PropertyListener(
|
||||
val owner: Any? = null,
|
||||
val action: (name: Name, oldItem: MetaItem<*>?, newItem: MetaItem<*>?) -> Unit
|
||||
)
|
||||
|
||||
abstract class AbstractVisualObject : VisualObject {
|
||||
|
||||
@Transient
|
||||
override var parent: VisualGroup? = null
|
||||
|
||||
protected abstract var properties: Config?
|
||||
|
||||
override var styles: List<String>
|
||||
get() = properties?.get(STYLE_KEY).stringList
|
||||
set(value) {
|
||||
//val allStyles = (field + value).distinct()
|
||||
setProperty(STYLE_KEY, Value.of(value))
|
||||
updateStyles(value)
|
||||
}
|
||||
|
||||
protected fun updateStyles(names: List<String>) {
|
||||
names.mapNotNull { findStyle(it) }.asSequence()
|
||||
.flatMap { it.items.asSequence() }
|
||||
.distinctBy { it.key }
|
||||
.forEach {
|
||||
propertyChanged(it.key.asName(), null, it.value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The config is initialized and assigned on-demand.
|
||||
* To avoid unnecessary allocations, one should access [properties] via [getProperty] instead.
|
||||
*/
|
||||
override val config: Config
|
||||
get() = properties ?: Config().also { config ->
|
||||
properties = config.apply { onChange(this, ::propertyChanged) }
|
||||
}
|
||||
|
||||
@Transient
|
||||
private val listeners = HashSet<PropertyListener>()
|
||||
|
||||
override fun propertyChanged(name: Name, before: MetaItem<*>?, after: MetaItem<*>?) {
|
||||
if (before != after) {
|
||||
for (l in listeners) {
|
||||
l.action(name, before, after)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPropertyChange(owner: Any?, action: (Name, before: MetaItem<*>?, after: MetaItem<*>?) -> Unit) {
|
||||
listeners.add(PropertyListener(owner, action))
|
||||
}
|
||||
|
||||
override fun removeChangeListener(owner: Any?) {
|
||||
listeners.removeAll { it.owner == owner }
|
||||
}
|
||||
|
||||
private var styleCache: Meta? = null
|
||||
|
||||
/**
|
||||
* Collect all styles for this object in a single cached meta
|
||||
*/
|
||||
protected val mergedStyles: Meta
|
||||
get() = styleCache ?: findAllStyles().merge().also {
|
||||
styleCache = it
|
||||
}
|
||||
|
||||
/**
|
||||
* All available properties in a layered form
|
||||
*/
|
||||
override fun allProperties(): Laminate = Laminate(properties, mergedStyles)
|
||||
|
||||
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? {
|
||||
return if (inherit) {
|
||||
properties?.get(name) ?: mergedStyles[name] ?: parent?.getProperty(name, inherit)
|
||||
} else {
|
||||
properties?.get(name) ?: mergedStyles[name]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//fun VisualObject.findStyle(styleName: Name): Meta? {
|
||||
// if (this is VisualGroup) {
|
||||
// val style = resolveStyle(styleName)
|
||||
// if (style != null) return style
|
||||
// }
|
||||
// return parent?.findStyle(styleName)
|
||||
//}
|
@ -1,39 +0,0 @@
|
||||
package hep.dataforge.vis
|
||||
|
||||
import hep.dataforge.context.*
|
||||
import hep.dataforge.meta.Meta
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
interface VisualFactory<T : VisualObject> {
|
||||
val type: KClass<T>
|
||||
operator fun invoke(
|
||||
context: Context,
|
||||
parent: VisualObject?,
|
||||
meta: Meta
|
||||
): T
|
||||
}
|
||||
|
||||
class Visual(meta: Meta) : AbstractPlugin(meta) {
|
||||
override val tag: PluginTag get() = Companion.tag
|
||||
|
||||
/**
|
||||
* Create a list of factories on first call and cache it
|
||||
*/
|
||||
val visualFactories by lazy {
|
||||
context.content<VisualFactory<*>>(VISUAL_FACTORY_TYPE).mapKeys { it.value.type }
|
||||
}
|
||||
|
||||
inline fun <reified T : VisualObject> buildVisual(parent: VisualObject?, meta: Meta): T? {
|
||||
return visualFactories[T::class]?.invoke(context, parent, meta) as T?
|
||||
}
|
||||
|
||||
companion object : PluginFactory<Visual> {
|
||||
override val tag: PluginTag = PluginTag(name = "visual", group = PluginTag.DATAFORGE_GROUP)
|
||||
override val type: KClass<out Visual> = Visual::class
|
||||
|
||||
override fun invoke(meta: Meta, context: Context): Visual =
|
||||
Visual(meta)
|
||||
|
||||
const val VISUAL_FACTORY_TYPE = "visual.factory"
|
||||
}
|
||||
}
|
@ -1,121 +0,0 @@
|
||||
package hep.dataforge.vis
|
||||
|
||||
import hep.dataforge.meta.Configurable
|
||||
import hep.dataforge.meta.Laminate
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.meta.MetaItem
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.names.toName
|
||||
import hep.dataforge.provider.Type
|
||||
import hep.dataforge.vis.VisualObject.Companion.TYPE
|
||||
import kotlinx.serialization.PolymorphicSerializer
|
||||
import kotlinx.serialization.Transient
|
||||
|
||||
//private fun Laminate.withTop(meta: Meta): Laminate = Laminate(listOf(meta) + layers)
|
||||
//private fun Laminate.withBottom(meta: Meta): Laminate = Laminate(layers + meta)
|
||||
|
||||
/**
|
||||
* A root type for display hierarchy
|
||||
*/
|
||||
@Type(TYPE)
|
||||
interface VisualObject : Configurable {
|
||||
|
||||
/**
|
||||
* The parent object of this one. If null, this one is a root.
|
||||
*/
|
||||
@Transient
|
||||
var parent: VisualGroup?
|
||||
|
||||
/**
|
||||
* All properties including styles and prototypes if present, but without inheritance
|
||||
*/
|
||||
fun allProperties(): Laminate
|
||||
|
||||
/**
|
||||
* Get property including or excluding parent properties
|
||||
*/
|
||||
fun getProperty(name: Name, inherit: Boolean): MetaItem<*>?
|
||||
|
||||
override fun getProperty(name: Name): MetaItem<*>? = getProperty(name, true)
|
||||
|
||||
/**
|
||||
* Trigger property invalidation event. If [name] is empty, notify that the whole object is changed
|
||||
*/
|
||||
fun propertyChanged(name: Name, before: MetaItem<*>?, after: MetaItem<*>?): Unit
|
||||
|
||||
fun propertyInvalidated(name: Name) = propertyChanged(name, null, null)
|
||||
|
||||
/**
|
||||
* Add listener triggering on property change
|
||||
*/
|
||||
fun onPropertyChange(owner: Any?, action: (Name, before: MetaItem<*>?, after: MetaItem<*>?) -> Unit): Unit
|
||||
|
||||
/**
|
||||
* Remove change listeners with given owner.
|
||||
*/
|
||||
fun removeChangeListener(owner: Any?)
|
||||
|
||||
/**
|
||||
* List of names of styles applied to this object. Order matters. Not inherited
|
||||
*/
|
||||
var styles: List<String>
|
||||
|
||||
companion object {
|
||||
const val TYPE = "visual"
|
||||
val STYLE_KEY = "@style".asName()
|
||||
|
||||
private val VISUAL_OBJECT_SERIALIZER = PolymorphicSerializer(VisualObject::class)
|
||||
|
||||
fun serializer() = VISUAL_OBJECT_SERIALIZER
|
||||
|
||||
//const val META_KEY = "@meta"
|
||||
//const val TAGS_KEY = "@tags"
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get [VisualObject] property using key as a String
|
||||
*/
|
||||
fun VisualObject.getProperty(key: String, inherit: Boolean = true): MetaItem<*>? = getProperty(key.toName(), inherit)
|
||||
|
||||
/**
|
||||
* Add style name to the list of styles to be resolved later. The style with given name does not necessary exist at the moment.
|
||||
*/
|
||||
fun VisualObject.useStyle(name: String) {
|
||||
styles = styles + name
|
||||
}
|
||||
|
||||
//private tailrec fun VisualObject.topGroup(): VisualGroup? {
|
||||
// val parent = this.parent
|
||||
// return if (parent == null) {
|
||||
// this as? VisualGroup
|
||||
// }
|
||||
// else {
|
||||
// parent.topGroup()
|
||||
// }
|
||||
//}
|
||||
//
|
||||
///**
|
||||
// * Add or update given style on a top-most reachable parent group and apply it to this object
|
||||
// */
|
||||
//fun VisualObject.useStyle(name: String, builder: MetaBuilder.() -> Unit) {
|
||||
// val styleName = name.toName()
|
||||
// topGroup()?.updateStyle(styleName, builder) ?: error("Can't find parent group for $this")
|
||||
// useStyle(styleName)
|
||||
//}
|
||||
|
||||
tailrec fun VisualObject.findStyle(name: String): Meta? =
|
||||
(this as? VisualGroup)?.styleSheet?.get(name) ?: parent?.findStyle(name)
|
||||
|
||||
fun VisualObject.findAllStyles(): Laminate = Laminate(styles.mapNotNull(::findStyle))
|
||||
|
||||
//operator fun VisualObject.get(name: Name): VisualObject?{
|
||||
// return when {
|
||||
// name.isEmpty() -> this
|
||||
// this is VisualGroup -> this[name]
|
||||
// else -> null
|
||||
// }
|
||||
//}
|
||||
|
@ -1,131 +0,0 @@
|
||||
package hep.dataforge.vis
|
||||
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.values.Value
|
||||
import kotlin.jvm.JvmName
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
import kotlin.properties.ReadWriteProperty
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
|
||||
/**
|
||||
* A delegate for display object properties
|
||||
*/
|
||||
class VisualObjectDelegate(
|
||||
val key: Name?,
|
||||
val default: MetaItem<*>?,
|
||||
val inherited: Boolean
|
||||
) : ReadWriteProperty<VisualObject, MetaItem<*>?> {
|
||||
override fun getValue(thisRef: VisualObject, property: KProperty<*>): MetaItem<*>? {
|
||||
val name = key ?: property.name.asName()
|
||||
return if (inherited) {
|
||||
thisRef.getProperty(name)
|
||||
} else {
|
||||
thisRef.config[name]
|
||||
} ?: default
|
||||
}
|
||||
|
||||
override fun setValue(thisRef: VisualObject, property: KProperty<*>, value: MetaItem<*>?) {
|
||||
val name = key ?: property.name.asName()
|
||||
thisRef.config[name] = value
|
||||
}
|
||||
}
|
||||
|
||||
class VisualObjectDelegateWrapper<T>(
|
||||
val obj: VisualObject,
|
||||
val key: Name?,
|
||||
val default: T,
|
||||
val inherited: Boolean,
|
||||
val write: Config.(name: Name, value: T) -> Unit = { name, value -> set(name, value) },
|
||||
val read: (MetaItem<*>?) -> T?
|
||||
) : ReadWriteProperty<Any?, T> {
|
||||
|
||||
//private var cachedName: Name? = null
|
||||
|
||||
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
|
||||
val name = key ?: property.name.asName()
|
||||
return read(obj.getProperty(name, inherited)) ?: default
|
||||
}
|
||||
|
||||
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
|
||||
val name = key ?: property.name.asName()
|
||||
obj.config[name] = value
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun VisualObject.value(default: Value? = null, name: Name? = null, inherited: Boolean = false) =
|
||||
VisualObjectDelegateWrapper(this, name, default, inherited) { it.value }
|
||||
|
||||
fun VisualObject.string(default: String? = null, name: Name? = null, inherited: Boolean = false) =
|
||||
VisualObjectDelegateWrapper(this, name, default, inherited) { it.string }
|
||||
|
||||
fun VisualObject.boolean(default: Boolean? = null, name: Name? = null, inherited: Boolean = false) =
|
||||
VisualObjectDelegateWrapper(this, name, default, inherited) { it.boolean }
|
||||
|
||||
fun VisualObject.number(default: Number? = null, name: Name? = null, inherited: Boolean = false) =
|
||||
VisualObjectDelegateWrapper(this, name, default, inherited) { it.number }
|
||||
|
||||
fun VisualObject.double(default: Double? = null, name: Name? = null, inherited: Boolean = false) =
|
||||
VisualObjectDelegateWrapper(this, name, default, inherited) { it.double }
|
||||
|
||||
fun VisualObject.int(default: Int? = null, name: Name? = null, inherited: Boolean = false) =
|
||||
VisualObjectDelegateWrapper(this, name, default, inherited) { it.int }
|
||||
|
||||
|
||||
fun VisualObject.node(name: Name? = null, inherited: Boolean = true) =
|
||||
VisualObjectDelegateWrapper(this, name, null, inherited) { it.node }
|
||||
|
||||
fun VisualObject.item(name: Name? = null, inherited: Boolean = true) =
|
||||
VisualObjectDelegateWrapper(this, name, null, inherited) { it }
|
||||
|
||||
//fun <T : Configurable> Configurable.spec(spec: Specification<T>, key: String? = null) = ChildConfigDelegate<T>(key) { spec.wrap(this) }
|
||||
|
||||
@JvmName("safeString")
|
||||
fun VisualObject.string(default: String, name: Name? = null, inherited: Boolean = false) =
|
||||
VisualObjectDelegateWrapper(this, name, default, inherited) { it.string }
|
||||
|
||||
@JvmName("safeBoolean")
|
||||
fun VisualObject.boolean(default: Boolean, name: Name? = null, inherited: Boolean = false) =
|
||||
VisualObjectDelegateWrapper(this, name, default, inherited) { it.boolean }
|
||||
|
||||
@JvmName("safeNumber")
|
||||
fun VisualObject.number(default: Number, name: Name? = null, inherited: Boolean = false) =
|
||||
VisualObjectDelegateWrapper(this, name, default, inherited) { it.number }
|
||||
|
||||
@JvmName("safeDouble")
|
||||
fun VisualObject.double(default: Double, name: Name? = null, inherited: Boolean = false) =
|
||||
VisualObjectDelegateWrapper(this, name, default, inherited) { it.double }
|
||||
|
||||
@JvmName("safeInt")
|
||||
fun VisualObject.int(default: Int, name: Name? = null, inherited: Boolean = false) =
|
||||
VisualObjectDelegateWrapper(this, name, default, inherited) { it.int }
|
||||
|
||||
|
||||
inline fun <reified E : Enum<E>> VisualObject.enum(default: E, name: Name? = null, inherited: Boolean = false) =
|
||||
VisualObjectDelegateWrapper(this, name, default, inherited) { item ->
|
||||
item.string?.let { enumValueOf<E>(it) }
|
||||
}
|
||||
|
||||
//merge properties
|
||||
|
||||
fun <T> VisualObject.merge(
|
||||
name: Name? = null,
|
||||
transformer: (Sequence<MetaItem<*>>) -> T
|
||||
): ReadOnlyProperty<VisualObject, T> {
|
||||
return object : ReadOnlyProperty<Any?, T> {
|
||||
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
|
||||
val actualName = name ?: property.name.asName()
|
||||
val sequence = sequence<MetaItem<*>> {
|
||||
var thisObj: VisualObject? = this@merge
|
||||
while (thisObj != null) {
|
||||
thisObj.config[actualName]?.let { yield(it) }
|
||||
thisObj = thisObj.parent
|
||||
}
|
||||
}
|
||||
return transformer(sequence)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
package hep.dataforge.vis
|
||||
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.meta.descriptors.ValueDescriptor
|
||||
import hep.dataforge.values.asValue
|
||||
|
||||
/**
|
||||
* Extension property to access the "widget" key of [ValueDescriptor]
|
||||
*/
|
||||
var ValueDescriptor.widget: Meta
|
||||
get() = getProperty("widget").node ?: Meta.EMPTY
|
||||
set(value) {
|
||||
setProperty("widget", value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension property to access the "widget.type" key of [ValueDescriptor]
|
||||
*/
|
||||
var ValueDescriptor.widgetType: String?
|
||||
get() = getProperty("widget.type").string
|
||||
set(value) {
|
||||
setProperty("widget.type", value?.asValue())
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
package hep.dataforge.js
|
||||
|
||||
import react.RComponent
|
||||
import kotlin.properties.ReadWriteProperty
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
fun <T> RComponent<*, *>.initState(init: () -> T): ReadWriteProperty<RComponent<*, *>, T> =
|
||||
object : ReadWriteProperty<RComponent<*, *>, T> {
|
||||
val pair = react.useState(init)
|
||||
override fun getValue(thisRef: RComponent<*, *>, property: KProperty<*>): T {
|
||||
return pair.first
|
||||
}
|
||||
|
||||
override fun setValue(thisRef: RComponent<*, *>, property: KProperty<*>, value: T) {
|
||||
pair.second(value)
|
||||
}
|
||||
}
|
@ -1,209 +0,0 @@
|
||||
package hep.dataforge.vis.editor
|
||||
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.meta.descriptors.ItemDescriptor
|
||||
import hep.dataforge.meta.descriptors.NodeDescriptor
|
||||
import hep.dataforge.meta.descriptors.defaultItem
|
||||
import hep.dataforge.meta.descriptors.get
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.NameToken
|
||||
import hep.dataforge.names.plus
|
||||
import hep.dataforge.values.asValue
|
||||
import kotlinx.html.InputType
|
||||
import kotlinx.html.classes
|
||||
import kotlinx.html.js.onChangeFunction
|
||||
import kotlinx.html.js.onClickFunction
|
||||
import org.w3c.dom.Element
|
||||
import org.w3c.dom.HTMLInputElement
|
||||
import org.w3c.dom.events.Event
|
||||
import react.RBuilder
|
||||
import react.RComponent
|
||||
import react.RProps
|
||||
import react.dom.*
|
||||
import react.setState
|
||||
|
||||
interface ConfigEditorProps : RProps {
|
||||
/**
|
||||
* Root config object - always non null
|
||||
*/
|
||||
var root: Config
|
||||
|
||||
/**
|
||||
* Full path to the displayed node in [root]. Could be empty
|
||||
*/
|
||||
var name: Name
|
||||
|
||||
/**
|
||||
* Root default
|
||||
*/
|
||||
var default: Meta?
|
||||
|
||||
/**
|
||||
* Root descriptor
|
||||
*/
|
||||
var descriptor: NodeDescriptor?
|
||||
|
||||
var listen: Boolean
|
||||
}
|
||||
|
||||
class ConfigEditorComponent : RComponent<ConfigEditorProps, TreeState>() {
|
||||
|
||||
override fun TreeState.init() {
|
||||
expanded = true
|
||||
}
|
||||
|
||||
override fun componentDidMount() {
|
||||
if (props.listen) {
|
||||
props.root.onChange(this) { name, _, _ ->
|
||||
if (name == props.name) {
|
||||
forceUpdate()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun componentWillUnmount() {
|
||||
props.root.removeListener(this)
|
||||
}
|
||||
|
||||
private val onClick: (Event) -> Unit = {
|
||||
setState {
|
||||
expanded = !expanded
|
||||
}
|
||||
}
|
||||
|
||||
private val onValueChange: (Event) -> Unit = {
|
||||
val value = (it.target as HTMLInputElement).value
|
||||
try {
|
||||
if(value.isEmpty()){
|
||||
props.root.remove(props.name)
|
||||
}
|
||||
props.root.setValue(props.name, value.asValue())
|
||||
} catch (ex: Exception) {
|
||||
console.error("Can't set config property ${props.name} to $value")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun RBuilder.render() {
|
||||
val item = props.root[props.name]
|
||||
val descriptorItem: ItemDescriptor? = props.descriptor?.get(props.name)
|
||||
val defaultItem = props.default?.get(props.name)
|
||||
val actualItem = item ?: defaultItem ?: descriptorItem?.defaultItem()
|
||||
val token = props.name.last()?.toString() ?: "Properties"
|
||||
|
||||
when (actualItem) {
|
||||
is MetaItem.NodeItem -> {
|
||||
div("d-inline-block text-truncate") {
|
||||
span("tree-caret") {
|
||||
attrs {
|
||||
if (state.expanded) {
|
||||
classes += "tree-caret-down"
|
||||
}
|
||||
onClickFunction = onClick
|
||||
}
|
||||
}
|
||||
span("tree-label") {
|
||||
+token
|
||||
attrs {
|
||||
if (item == null) {
|
||||
classes += "tree-label-inactive"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (state.expanded) {
|
||||
ul("tree") {
|
||||
val keys = buildSet<NameToken> {
|
||||
item?.node?.items?.keys?.let { addAll(it) }
|
||||
defaultItem?.node?.items?.keys?.let { addAll(it) }
|
||||
(descriptorItem as? NodeDescriptor)?.items?.keys?.forEach {
|
||||
add(NameToken(it))
|
||||
}
|
||||
}
|
||||
|
||||
keys.forEach { token ->
|
||||
li("tree-item") {
|
||||
child(ConfigEditorComponent::class) {
|
||||
attrs {
|
||||
root = props.root
|
||||
name = props.name + token
|
||||
this.default = props.default
|
||||
this.descriptor = props.descriptor
|
||||
listen = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is MetaItem.ValueItem -> {
|
||||
div("d-inline-block text-truncate") {
|
||||
div("row") {
|
||||
div("col") {
|
||||
p("tree-label") {
|
||||
+token
|
||||
attrs {
|
||||
if (item == null) {
|
||||
classes += "tree-label-inactive"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
div("col") {
|
||||
div("float-right") {
|
||||
input(type = InputType.text) {
|
||||
attrs {
|
||||
defaultValue = actualItem.value.string
|
||||
onChangeFunction = onValueChange
|
||||
}
|
||||
}
|
||||
//+actualItem.value.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun Element.configEditor(config: Config, descriptor: NodeDescriptor? = null, default: Meta? = null) {
|
||||
render(this) {
|
||||
child(ConfigEditorComponent::class) {
|
||||
attrs {
|
||||
root = config
|
||||
name = Name.EMPTY
|
||||
this.descriptor = descriptor
|
||||
this.default = default
|
||||
listen = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun RBuilder.configEditor(config: Config, descriptor: NodeDescriptor? = null, default: Meta? = null) {
|
||||
child(ConfigEditorComponent::class) {
|
||||
attrs {
|
||||
root = config
|
||||
name = Name.EMPTY
|
||||
this.descriptor = descriptor
|
||||
this.default = default
|
||||
listen = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun RBuilder.configEditor(obj: Configurable, descriptor: NodeDescriptor? = obj.descriptor, default: Meta? = null) {
|
||||
child(ConfigEditorComponent::class) {
|
||||
attrs {
|
||||
root = obj.config
|
||||
name = Name.EMPTY
|
||||
this.descriptor = descriptor
|
||||
this.default = default
|
||||
listen = true
|
||||
}
|
||||
}
|
||||
}
|
@ -1,111 +0,0 @@
|
||||
package hep.dataforge.vis.editor
|
||||
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.plus
|
||||
import hep.dataforge.vis.VisualGroup
|
||||
import hep.dataforge.vis.VisualObject
|
||||
import hep.dataforge.vis.isEmpty
|
||||
import kotlinx.html.classes
|
||||
import kotlinx.html.js.onClickFunction
|
||||
import org.w3c.dom.Element
|
||||
import org.w3c.dom.events.Event
|
||||
import react.*
|
||||
import react.dom.*
|
||||
|
||||
interface ObjectTreeProps : RProps {
|
||||
var name: Name
|
||||
var obj: VisualObject
|
||||
var clickCallback: (Name) -> Unit
|
||||
}
|
||||
|
||||
interface TreeState : RState {
|
||||
var expanded: Boolean
|
||||
}
|
||||
|
||||
class ObjectTreeComponent : RComponent<ObjectTreeProps, TreeState>() {
|
||||
|
||||
override fun TreeState.init() {
|
||||
expanded = false
|
||||
}
|
||||
|
||||
private val onClick: (Event) -> Unit = {
|
||||
setState {
|
||||
expanded = !expanded
|
||||
}
|
||||
}
|
||||
|
||||
override fun RBuilder.render() {
|
||||
val token = props.name.last()?.toString() ?: "World"
|
||||
val obj = props.obj
|
||||
|
||||
//display as node if any child is visible
|
||||
if (obj is VisualGroup && obj.children.keys.any { !it.body.startsWith("@") }) {
|
||||
div("d-inline-block text-truncate") {
|
||||
span("tree-caret") {
|
||||
attrs {
|
||||
if (state.expanded) {
|
||||
classes += "tree-caret-down"
|
||||
}
|
||||
onClickFunction = onClick
|
||||
}
|
||||
}
|
||||
a("#",classes = "tree-label") {
|
||||
+token
|
||||
attrs {
|
||||
onClickFunction = { props.clickCallback(props.name) }
|
||||
}
|
||||
}
|
||||
}
|
||||
if (state.expanded) {
|
||||
ul("tree") {
|
||||
obj.children.entries
|
||||
.filter { !it.key.toString().startsWith("@") } // ignore statics and other hidden children
|
||||
.sortedBy { (it.value as? VisualGroup)?.isEmpty ?: true }
|
||||
.forEach { (childToken, child) ->
|
||||
li("tree-item") {
|
||||
child(ObjectTreeComponent::class) {
|
||||
attrs {
|
||||
name = props.name + childToken
|
||||
this.obj = child
|
||||
clickCallback = props.clickCallback
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
div("d-inline-block text-truncate") {
|
||||
span("tree-leaf") {}
|
||||
a("#",classes = "tree-label") {
|
||||
+token
|
||||
attrs {
|
||||
onClickFunction = { props.clickCallback(props.name) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun RBuilder.objectTree(
|
||||
obj: VisualObject,
|
||||
clickCallback: (Name) -> Unit = {}
|
||||
) = card("Object tree") {
|
||||
child(ObjectTreeComponent::class) {
|
||||
attrs {
|
||||
name = Name.EMPTY
|
||||
this.obj = obj
|
||||
this.clickCallback = clickCallback
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Element.objectTree(
|
||||
obj: VisualObject,
|
||||
clickCallback: (Name) -> Unit = {}
|
||||
) {
|
||||
render(this) {
|
||||
objectTree(obj, clickCallback)
|
||||
}
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
package hep.dataforge.vis.editor
|
||||
|
||||
import kotlinx.html.*
|
||||
import kotlinx.html.js.div
|
||||
import org.w3c.dom.HTMLElement
|
||||
import react.RBuilder
|
||||
import react.ReactElement
|
||||
import react.dom.div
|
||||
import react.dom.h3
|
||||
|
||||
inline fun TagConsumer<HTMLElement>.card(title: String, crossinline block: TagConsumer<HTMLElement>.() -> Unit) {
|
||||
div("card w-100") {
|
||||
div("card-body") {
|
||||
h3(classes = "card-title") { +title }
|
||||
block()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline fun RBuilder.card(title: String, crossinline block: RBuilder.() -> Unit): ReactElement = div("card w-100") {
|
||||
div("card-body") {
|
||||
h3(classes = "card-title") { +title }
|
||||
block()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun TagConsumer<HTMLElement>.accordion(id: String, elements: Map<String, DIV.() -> Unit>) {
|
||||
div("container-fluid") {
|
||||
div("accordion") {
|
||||
this.id = id
|
||||
elements.entries.forEachIndexed { index, (title, builder) ->
|
||||
val headerID = "${id}-${index}-heading"
|
||||
val collapseID = "${id}-${index}-collapse"
|
||||
div("card") {
|
||||
div("card-header") {
|
||||
this.id = headerID
|
||||
h5("mb-0") {
|
||||
button(classes = "btn btn-link collapsed", type = ButtonType.button) {
|
||||
attributes["data-toggle"] = "collapse"
|
||||
attributes["data-target"] = "#$collapseID"
|
||||
attributes["aria-expanded"] = "false"
|
||||
attributes["aria-controls"] = collapseID
|
||||
+title
|
||||
}
|
||||
}
|
||||
}
|
||||
div("collapse") {
|
||||
this.id = collapseID
|
||||
attributes["aria-labelledby"] = headerID
|
||||
attributes["data-parent"] = "#$id"
|
||||
div("card-body", block = builder)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AccordionBuilder {
|
||||
private val map = HashMap<String, DIV.() -> Unit>()
|
||||
fun entry(title: String, block: DIV.() -> Unit) {
|
||||
map[title] = block
|
||||
}
|
||||
|
||||
fun build(consumer: TagConsumer<HTMLElement>, id: String) {
|
||||
consumer.accordion(id, map)
|
||||
}
|
||||
}
|
||||
|
||||
fun TagConsumer<HTMLElement>.accordion(id: String, block: AccordionBuilder.() -> Unit) {
|
||||
AccordionBuilder().apply(block).build(this, id)
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
package hep.dataforge.vis.editor
|
||||
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.plus
|
||||
import hep.dataforge.vis.VisualGroup
|
||||
import hep.dataforge.vis.VisualObject
|
||||
import hep.dataforge.vis.isEmpty
|
||||
import kotlinx.html.TagConsumer
|
||||
import kotlinx.html.dom.append
|
||||
import kotlinx.html.js.*
|
||||
import org.w3c.dom.Element
|
||||
import org.w3c.dom.HTMLElement
|
||||
import org.w3c.dom.HTMLSpanElement
|
||||
import kotlin.dom.clear
|
||||
|
||||
//fun Element.displayObjectTree(
|
||||
// obj: VisualObject,
|
||||
// clickCallback: (Name) -> Unit = {}
|
||||
//) {
|
||||
// clear()
|
||||
// append {
|
||||
// card("Object tree") {
|
||||
// subTree(Name.EMPTY, obj, clickCallback)
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//
|
||||
private fun TagConsumer<HTMLElement>.subTree(
|
||||
name: Name,
|
||||
obj: VisualObject,
|
||||
clickCallback: (Name) -> Unit
|
||||
) {
|
||||
val token = name.last()?.toString()?:"World"
|
||||
|
||||
//display as node if any child is visible
|
||||
if (obj is VisualGroup && obj.children.keys.any { !it.body.startsWith("@") }) {
|
||||
lateinit var toggle: HTMLSpanElement
|
||||
div("d-inline-block text-truncate") {
|
||||
toggle = span("objTree-caret")
|
||||
label("objTree-label") {
|
||||
+token
|
||||
onClickFunction = { clickCallback(name) }
|
||||
}
|
||||
}
|
||||
val subtree = ul("objTree-subtree")
|
||||
toggle.onclick = {
|
||||
toggle.classList.toggle("objTree-caret-down")
|
||||
subtree.apply {
|
||||
//If expanded, add children dynamically
|
||||
if (toggle.classList.contains("objTree-caret-down")) {
|
||||
obj.children.entries
|
||||
.filter { !it.key.toString().startsWith("@") } // ignore statics and other hidden children
|
||||
.sortedBy { (it.value as? VisualGroup)?.isEmpty ?: true }
|
||||
.forEach { (childToken, child) ->
|
||||
append {
|
||||
li().apply {
|
||||
subTree(name + childToken, child, clickCallback)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// if not, clear them to conserve memory on very long lists
|
||||
this.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
div("d-inline-block text-truncate") {
|
||||
span("objTree-leaf")
|
||||
label("objTree-label") {
|
||||
+token
|
||||
onClickFunction = { clickCallback(name) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,185 +0,0 @@
|
||||
@file:Suppress(
|
||||
"INTERFACE_WITH_SUPERCLASS",
|
||||
"OVERRIDING_FINAL_MEMBER",
|
||||
"RETURN_TYPE_MISMATCH_ON_OVERRIDE",
|
||||
"CONFLICTING_OVERLOADS",
|
||||
"EXTERNAL_DELEGATION"
|
||||
)
|
||||
|
||||
package hep.dataforge.vis.editor
|
||||
|
||||
import org.w3c.dom.HTMLElement
|
||||
|
||||
external interface Node {
|
||||
var field: String
|
||||
var value: String? get() = definedExternally; set(value) = definedExternally
|
||||
var path: dynamic
|
||||
}
|
||||
|
||||
external interface NodeName {
|
||||
var path: Array<String>
|
||||
var type: dynamic /* 'object' | 'array' */
|
||||
var size: Number
|
||||
}
|
||||
|
||||
external interface ValidationError {
|
||||
var path: dynamic
|
||||
var message: String
|
||||
}
|
||||
|
||||
external interface Template {
|
||||
var text: String
|
||||
var title: String
|
||||
var className: String? get() = definedExternally; set(value) = definedExternally
|
||||
var field: String
|
||||
var value: Any
|
||||
}
|
||||
|
||||
external interface `T$6` {
|
||||
var startFrom: Number
|
||||
var options: Array<String>
|
||||
}
|
||||
|
||||
external interface AutoCompleteOptions {
|
||||
var confirmKeys: Array<Number>? get() = definedExternally; set(value) = definedExternally
|
||||
var caseSensitive: Boolean? get() = definedExternally; set(value) = definedExternally
|
||||
// var getOptions: AutoCompleteOptionsGetter? get() = definedExternally; set(value) = definedExternally
|
||||
}
|
||||
|
||||
external interface SelectionPosition {
|
||||
var row: Number
|
||||
var column: Number
|
||||
}
|
||||
|
||||
external interface SerializableNode {
|
||||
var value: Any
|
||||
var path: dynamic
|
||||
}
|
||||
|
||||
external interface Color {
|
||||
var rgba: Array<Number>
|
||||
var hsla: Array<Number>
|
||||
var rgbString: String
|
||||
var rgbaString: String
|
||||
var hslString: String
|
||||
var hslaString: String
|
||||
var hex: String
|
||||
}
|
||||
|
||||
//external interface `T$0` {
|
||||
// var field: Boolean
|
||||
// var value: Boolean
|
||||
//}
|
||||
//
|
||||
//external interface `T$1` {
|
||||
// @nativeGetter
|
||||
// operator fun get(key: String): String?
|
||||
//
|
||||
// @nativeSetter
|
||||
// operator fun set(key: String, value: String)
|
||||
//}
|
||||
|
||||
//external interface Languages {
|
||||
// @nativeGetter
|
||||
// operator fun get(lang: String): `T$1`?
|
||||
//
|
||||
// @nativeSetter
|
||||
// operator fun set(lang: String, value: `T$1`)
|
||||
//}
|
||||
|
||||
external interface JSONEditorOptions {
|
||||
// var ace: AceAjax.Ace? get() = definedExternally; set(value) = definedExternally
|
||||
// var ajv: Ajv? get() = definedExternally; set(value) = definedExternally
|
||||
var onChange: (() -> Unit)? get() = definedExternally; set(value) = definedExternally
|
||||
var onChangeJSON: ((json: Any) -> Unit)? get() = definedExternally; set(value) = definedExternally
|
||||
var onChangeText: ((jsonString: String) -> Unit)? get() = definedExternally; set(value) = definedExternally
|
||||
var onEditable: ((node: Node) -> dynamic)? get() = definedExternally; set(value) = definedExternally
|
||||
var onError: ((error: Error) -> Unit)? get() = definedExternally; set(value) = definedExternally
|
||||
var onModeChange: ((newMode: dynamic /* 'tree' | 'view' | 'form' | 'code' | 'text' */, oldMode: dynamic /* 'tree' | 'view' | 'form' | 'code' | 'text' */) -> Unit)? get() = definedExternally; set(value) = definedExternally
|
||||
var onNodeName: ((nodeName: NodeName) -> String?)? get() = definedExternally; set(value) = definedExternally
|
||||
var onValidate: ((json: Any) -> dynamic)? get() = definedExternally; set(value) = definedExternally
|
||||
var escapeUnicode: Boolean? get() = definedExternally; set(value) = definedExternally
|
||||
var sortObjectKeys: Boolean? get() = definedExternally; set(value) = definedExternally
|
||||
var history: Boolean? get() = definedExternally; set(value) = definedExternally
|
||||
var mode: dynamic /* 'tree' | 'view' | 'form' | 'code' | 'text' */
|
||||
var modes: Array<dynamic /* 'tree' | 'view' | 'form' | 'code' | 'text' */>? get() = definedExternally; set(value) = definedExternally
|
||||
var name: String? get() = definedExternally; set(value) = definedExternally
|
||||
var schema: Any? get() = definedExternally; set(value) = definedExternally
|
||||
var schemaRefs: Any? get() = definedExternally; set(value) = definedExternally
|
||||
var search: Boolean? get() = definedExternally; set(value) = definedExternally
|
||||
var indentation: Number? get() = definedExternally; set(value) = definedExternally
|
||||
var theme: String? get() = definedExternally; set(value) = definedExternally
|
||||
var templates: Array<Template>? get() = definedExternally; set(value) = definedExternally
|
||||
var autocomplete: AutoCompleteOptions? get() = definedExternally; set(value) = definedExternally
|
||||
var mainMenuBar: Boolean? get() = definedExternally; set(value) = definedExternally
|
||||
var navigationBar: Boolean? get() = definedExternally; set(value) = definedExternally
|
||||
var statusBar: Boolean? get() = definedExternally; set(value) = definedExternally
|
||||
var onTextSelectionChange: ((start: SelectionPosition, end: SelectionPosition, text: String) -> Unit)? get() = definedExternally; set(value) = definedExternally
|
||||
var onSelectionChange: ((start: SerializableNode, end: SerializableNode) -> Unit)? get() = definedExternally; set(value) = definedExternally
|
||||
var onEvent: ((node: Node, event: String) -> Unit)? get() = definedExternally; set(value) = definedExternally
|
||||
var colorPicker: Boolean? get() = definedExternally; set(value) = definedExternally
|
||||
var onColorPicker: ((parent: HTMLElement, color: String, onChange: (color: Color) -> Unit) -> Unit)? get() = definedExternally; set(value) = definedExternally
|
||||
var timestampTag: Boolean? get() = definedExternally; set(value) = definedExternally
|
||||
var language: String? get() = definedExternally; set(value) = definedExternally
|
||||
//var languages: Languages? get() = definedExternally; set(value) = definedExternally
|
||||
var modalAnchor: HTMLElement? get() = definedExternally; set(value) = definedExternally
|
||||
var enableSort: Boolean? get() = definedExternally; set(value) = definedExternally
|
||||
var enableTransform: Boolean? get() = definedExternally; set(value) = definedExternally
|
||||
var maxVisibleChilds: Number? get() = definedExternally; set(value) = definedExternally
|
||||
}
|
||||
|
||||
external interface JsonPath {
|
||||
var path: dynamic
|
||||
}
|
||||
|
||||
external interface EditorSelection {
|
||||
var start: SerializableNode
|
||||
var end: SerializableNode
|
||||
}
|
||||
|
||||
external interface TextSelection {
|
||||
var start: SelectionPosition
|
||||
var end: SelectionPosition
|
||||
var text: String
|
||||
}
|
||||
|
||||
@JsModule("jsoneditor")
|
||||
@JsNonModule
|
||||
external open class JSONEditor(
|
||||
container: HTMLElement,
|
||||
options: JSONEditorOptions? = definedExternally /* null */,
|
||||
json: dynamic = definedExternally /* null */
|
||||
) {
|
||||
open fun collapseAll()
|
||||
open fun destroy()
|
||||
open fun expandAll()
|
||||
open fun focus()
|
||||
open fun get(): Any
|
||||
open fun getMode(): dynamic /* 'tree' | 'view' | 'form' | 'code' | 'text' */
|
||||
open fun getName(): String?
|
||||
open fun getNodesByRange(start: JsonPath, end: JsonPath): Array<SerializableNode>
|
||||
open fun getSelection(): EditorSelection
|
||||
open fun getText(): String
|
||||
open fun getTextSelection(): TextSelection
|
||||
open fun refresh()
|
||||
open fun set(json: Any)
|
||||
open fun setMode(mode: String /* 'tree' */)
|
||||
open fun setMode(mode: String /* 'view' */)
|
||||
open fun setMode(mode: String /* 'form' */)
|
||||
open fun setMode(mode: String /* 'code' */)
|
||||
open fun setMode(mode: String /* 'text' */)
|
||||
open fun setName(name: String? = definedExternally /* null */)
|
||||
open fun setSchema(schema: Any?, schemaRefs: Any? = definedExternally /* null */)
|
||||
open fun setSelection(start: JsonPath, end: JsonPath)
|
||||
open fun setText(jsonString: String)
|
||||
open fun setTextSelection(start: SelectionPosition, end: SelectionPosition)
|
||||
open fun update(json: Any)
|
||||
open fun updateText(jsonString: String)
|
||||
|
||||
companion object {
|
||||
var VALID_OPTIONS: Array<String>
|
||||
// var ace: AceAjax.Ace
|
||||
// var Ajv: Ajv
|
||||
var VanillaPicker: Any
|
||||
}
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
package hep.dataforge.vis.editor
|
||||
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.meta.MetaBuilder
|
||||
import hep.dataforge.meta.descriptors.NodeDescriptor
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.isEmpty
|
||||
import hep.dataforge.vis.VisualObject
|
||||
import org.w3c.dom.Element
|
||||
import react.RBuilder
|
||||
import react.ReactElement
|
||||
import react.dom.li
|
||||
import react.dom.nav
|
||||
import react.dom.ol
|
||||
import react.dom.render
|
||||
import kotlin.collections.set
|
||||
|
||||
////FIXME something rotten in JS-Meta converter
|
||||
//fun Meta.toDynamic() = JSON.parse<dynamic>(toJson().toString())
|
||||
//
|
||||
////TODO add node descriptor instead of configuring property selector
|
||||
//fun Element.displayPropertyEditor(
|
||||
// name: Name,
|
||||
// item: VisualObject,
|
||||
// propertySelector: (VisualObject) -> Meta = { it.config }
|
||||
//) {
|
||||
// clear()
|
||||
//
|
||||
// append {
|
||||
// card("Properties") {
|
||||
// if (!name.isEmpty()) {
|
||||
// nav {
|
||||
// attributes["aria-label"] = "breadcrumb"
|
||||
// ol("breadcrumb") {
|
||||
// name.tokens.forEach { token ->
|
||||
// li("breadcrumb-item") {
|
||||
// +token.toString()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// val dMeta: dynamic = propertySelector(item).toDynamic()
|
||||
// val options: JSONEditorOptions = jsObject {
|
||||
// mode = "form"
|
||||
// onChangeJSON = { item.config.update(DynamicMeta(it.asDynamic())) }
|
||||
// }
|
||||
// JSONEditor(div(), options, dMeta)
|
||||
// }
|
||||
//
|
||||
// val styles = item.styles
|
||||
// if (styles.isNotEmpty()) {
|
||||
// card("Styles") {
|
||||
// item.styles.forEach { style ->
|
||||
// val styleMeta = item.findStyle(style)
|
||||
// h4("container") { +style }
|
||||
// if (styleMeta != null) {
|
||||
// div("container").apply {
|
||||
// val options: JSONEditorOptions = jsObject {
|
||||
// mode = "view"
|
||||
// }
|
||||
// JSONEditor(
|
||||
// this,
|
||||
// options,
|
||||
// styleMeta.toDynamic()
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
fun RBuilder.visualPropertyEditor(
|
||||
path: Name,
|
||||
item: VisualObject,
|
||||
descriptor: NodeDescriptor? = item.descriptor,
|
||||
title: String = "Properties",
|
||||
default: MetaBuilder.() -> Unit = {}
|
||||
): ReactElement = card(title) {
|
||||
if (!path.isEmpty()) {
|
||||
nav {
|
||||
attrs {
|
||||
attributes["aria-label"] = "breadcrumb"
|
||||
}
|
||||
ol("breadcrumb") {
|
||||
path.tokens.forEach { token ->
|
||||
li("breadcrumb-item") {
|
||||
+token.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
configEditor(item, descriptor, Meta(default))
|
||||
}
|
||||
|
||||
fun Element.visualPropertyEditor(
|
||||
path: Name,
|
||||
item: VisualObject,
|
||||
descriptor: NodeDescriptor? = item.descriptor,
|
||||
title: String = "Properties",
|
||||
default: MetaBuilder.() -> Unit = {}
|
||||
) = render(this) {
|
||||
visualPropertyEditor(path, item, descriptor, title, default)
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
/* Remove default bullets */
|
||||
ul, .tree {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
/* Style the caret/arrow */
|
||||
.tree-caret {
|
||||
cursor: pointer;
|
||||
user-select: none; /* Prevent text selection */
|
||||
}
|
||||
|
||||
/* Create the caret/arrow with a unicode, and style it */
|
||||
.tree-caret::before {
|
||||
content: "\25B6";
|
||||
color: black;
|
||||
display: inline-block;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.tree-leaf{
|
||||
user-select: none;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.tree-leaf::before {
|
||||
content: "\25C6";
|
||||
color: black;
|
||||
display: inline-block;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
|
||||
/* Rotate the caret/arrow icon when clicked on (using JavaScript) */
|
||||
.tree-caret-down::before {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.tree-label-inactive {
|
||||
background-color: lightgrey;
|
||||
color: gray;
|
||||
display: inline-block;
|
||||
}
|
@ -12,7 +12,7 @@ plugins {
|
||||
val kotlinVersion: String by rootProject.extra
|
||||
|
||||
dependencies {
|
||||
implementation(project(":dataforge-vis-spatial-js"))
|
||||
implementation(project(":visionforge-spatial-js"))
|
||||
testCompile(kotlin("test-js"))
|
||||
}
|
||||
|
||||
|
@ -35,7 +35,7 @@ fun main() {
|
||||
}
|
||||
|
||||
fun start(state: dynamic): ApplicationBase? {
|
||||
return if (document.body?.hasClass("testApp") == true) {
|
||||
return if (document.body?.hasClass("application") == true) {
|
||||
val application = JSRootDemoApp()
|
||||
|
||||
@Suppress("UnsafeCastFromDynamic")
|
||||
|
@ -6,9 +6,9 @@
|
||||
<title>Three js demo for particle physics</title>
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
|
||||
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
|
||||
<script type="text/javascript" src="dataforge-vis-spatial-gdml-0.1.0-dev.js"></script>
|
||||
<script type="text/javascript" src="visionforge-spatial-gdml-0.1.0-dev.js"></script>
|
||||
</head>
|
||||
<body class="testApp">
|
||||
<body class="application">
|
||||
<div class="container" id="drop_zone" data-toggle="tooltip" data-placement="right"
|
||||
title="Для загрузки данных в текстовом формате, надо перетащить файл сюда">
|
||||
Загрузить данные
|
||||
|
@ -1,81 +0,0 @@
|
||||
package hep.dataforge.vis.spatial
|
||||
|
||||
import hep.dataforge.context.AbstractPlugin
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.context.PluginFactory
|
||||
import hep.dataforge.context.PluginTag
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.toName
|
||||
import hep.dataforge.vis.SimpleVisualGroup
|
||||
import hep.dataforge.vis.Visual
|
||||
import hep.dataforge.vis.VisualObject
|
||||
import kotlinx.serialization.UnstableDefault
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonConfiguration
|
||||
import kotlinx.serialization.modules.SerializersModule
|
||||
import kotlinx.serialization.modules.contextual
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
class Visual3D(meta: Meta) : AbstractPlugin(meta) {
|
||||
|
||||
val visual by require(Visual)
|
||||
|
||||
override val tag: PluginTag get() = Companion.tag
|
||||
|
||||
override fun provideTop(target: String): Map<Name, Any> = if (target == Visual.VISUAL_FACTORY_TYPE) {
|
||||
mapOf(Box.TYPE_NAME.toName() to Box)
|
||||
} else {
|
||||
super.provideTop(target)
|
||||
}
|
||||
|
||||
|
||||
companion object : PluginFactory<Visual3D> {
|
||||
override val tag: PluginTag = PluginTag(name = "visual.spatial", group = PluginTag.DATAFORGE_GROUP)
|
||||
override val type: KClass<out Visual3D> = Visual3D::class
|
||||
override fun invoke(meta: Meta, context: Context): Visual3D = Visual3D(meta)
|
||||
|
||||
val serialModule = SerializersModule {
|
||||
contextual(Point3DSerializer)
|
||||
contextual(Point2DSerializer)
|
||||
|
||||
polymorphic(VisualObject::class, VisualObject3D::class) {
|
||||
subclass(SimpleVisualGroup.serializer())
|
||||
subclass(VisualGroup3D.serializer())
|
||||
subclass(Proxy.serializer())
|
||||
subclass(Composite.serializer())
|
||||
subclass(Tube.serializer())
|
||||
subclass(Box.serializer())
|
||||
subclass(Convex.serializer())
|
||||
subclass(Extruded.serializer())
|
||||
subclass(PolyLine.serializer())
|
||||
subclass(Label3D.serializer())
|
||||
subclass(Sphere.serializer())
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(UnstableDefault::class)
|
||||
internal val json = Json(
|
||||
JsonConfiguration(
|
||||
prettyPrint = true,
|
||||
useArrayPolymorphism = false,
|
||||
encodeDefaults = false,
|
||||
ignoreUnknownKeys = true
|
||||
),
|
||||
context = serialModule
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun VisualObject3D.update(meta: Meta) {
|
||||
fun Meta.toVector(default: Float = 0f) = Point3D(
|
||||
this[VisualObject3D.x].float ?: default,
|
||||
this[VisualObject3D.y].float ?: default,
|
||||
this[VisualObject3D.z].float ?: default
|
||||
)
|
||||
|
||||
meta[VisualObject3D.position].node?.toVector()?.let { position = it }
|
||||
meta[VisualObject3D.rotation].node?.toVector()?.let { rotation = it }
|
||||
meta[VisualObject3D.scale].node?.toVector(1f)?.let { scale = it }
|
||||
meta["properties"].node?.let { configure(it) }
|
||||
}
|
@ -1,204 +0,0 @@
|
||||
@file:UseSerializers(Point3DSerializer::class)
|
||||
|
||||
package hep.dataforge.vis.spatial
|
||||
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.meta.descriptors.NodeDescriptor
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.names.plus
|
||||
import hep.dataforge.output.Renderer
|
||||
import hep.dataforge.values.ValueType
|
||||
import hep.dataforge.values.asValue
|
||||
import hep.dataforge.vis.VisualObject
|
||||
import hep.dataforge.vis.spatial.VisualObject3D.Companion.DETAIL_KEY
|
||||
import hep.dataforge.vis.spatial.VisualObject3D.Companion.IGNORE_KEY
|
||||
import hep.dataforge.vis.spatial.VisualObject3D.Companion.LAYER_KEY
|
||||
import hep.dataforge.vis.spatial.VisualObject3D.Companion.VISIBLE_KEY
|
||||
import kotlinx.serialization.UseSerializers
|
||||
|
||||
/**
|
||||
* Interface for 3-dimensional [VisualObject]
|
||||
*/
|
||||
interface VisualObject3D : VisualObject {
|
||||
var position: Point3D?
|
||||
var rotation: Point3D?
|
||||
var scale: Point3D?
|
||||
|
||||
companion object {
|
||||
|
||||
val VISIBLE_KEY = "visible".asName()
|
||||
|
||||
// val SELECTED_KEY = "selected".asName()
|
||||
val DETAIL_KEY = "detail".asName()
|
||||
val LAYER_KEY = "layer".asName()
|
||||
val IGNORE_KEY = "ignore".asName()
|
||||
|
||||
val GEOMETRY_KEY = "geometry".asName()
|
||||
|
||||
val x = "x".asName()
|
||||
val y = "y".asName()
|
||||
val z = "z".asName()
|
||||
|
||||
val position = "pos".asName()
|
||||
|
||||
val xPos = position + x
|
||||
val yPos = position + y
|
||||
val zPos = position + z
|
||||
|
||||
val rotation = "rotation".asName()
|
||||
|
||||
val xRotation = rotation + x
|
||||
val yRotation = rotation + y
|
||||
val zRotation = rotation + z
|
||||
|
||||
val rotationOrder = rotation + "order"
|
||||
|
||||
val scale = "scale".asName()
|
||||
|
||||
val xScale = scale + x
|
||||
val yScale = scale + y
|
||||
val zScale = scale + z
|
||||
|
||||
val descriptor by lazy {
|
||||
NodeDescriptor {
|
||||
defineValue(VISIBLE_KEY) {
|
||||
type(ValueType.BOOLEAN)
|
||||
default(true)
|
||||
}
|
||||
|
||||
defineItem(Material3D.MATERIAL_KEY.toString(), Material3D.descriptor)
|
||||
|
||||
// Material3D.MATERIAL_COLOR_KEY put "#ffffff"
|
||||
// Material3D.MATERIAL_OPACITY_KEY put 1.0
|
||||
// Material3D.MATERIAL_WIREFRAME_KEY put false
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Count number of layers to the top object. Return 1 if this is top layer
|
||||
*/
|
||||
var VisualObject3D.layer: Int
|
||||
get() = getProperty(LAYER_KEY).int ?: 0
|
||||
set(value) {
|
||||
setProperty(LAYER_KEY, value.asValue())
|
||||
}
|
||||
|
||||
fun Renderer<VisualObject3D>.render(meta: Meta = Meta.EMPTY, action: VisualGroup3D.() -> Unit) =
|
||||
render(VisualGroup3D().apply(action), meta)
|
||||
|
||||
// Common properties
|
||||
|
||||
enum class RotationOrder {
|
||||
XYZ,
|
||||
YZX,
|
||||
ZXY,
|
||||
XZY,
|
||||
YXZ,
|
||||
ZYX
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotation order
|
||||
*/
|
||||
var VisualObject3D.rotationOrder: RotationOrder
|
||||
get() = getProperty(VisualObject3D.rotationOrder).enum<RotationOrder>() ?: RotationOrder.XYZ
|
||||
set(value) = setProperty(VisualObject3D.rotationOrder, value.name.asValue())
|
||||
|
||||
|
||||
/**
|
||||
* Preferred number of polygons for displaying the object. If not defined, uses shape or renderer default. Not inherited
|
||||
*/
|
||||
var VisualObject3D.detail: Int?
|
||||
get() = getProperty(DETAIL_KEY, false).int
|
||||
set(value) = setProperty(DETAIL_KEY, value?.asValue())
|
||||
|
||||
var VisualObject.visible: Boolean?
|
||||
get() = getProperty(VISIBLE_KEY).boolean
|
||||
set(value) = setProperty(VISIBLE_KEY, value?.asValue())
|
||||
|
||||
/**
|
||||
* If this property is true, the object will be ignored on render.
|
||||
* Property is not inherited.
|
||||
*/
|
||||
var VisualObject.ignore: Boolean?
|
||||
get() = getProperty(IGNORE_KEY, false).boolean
|
||||
set(value) = setProperty(IGNORE_KEY, value?.asValue())
|
||||
|
||||
//var VisualObject.selected: Boolean?
|
||||
// get() = getProperty(SELECTED_KEY).boolean
|
||||
// set(value) = setProperty(SELECTED_KEY, value)
|
||||
|
||||
private fun VisualObject3D.position(): Point3D =
|
||||
position ?: Point3D(0.0, 0.0, 0.0).also { position = it }
|
||||
|
||||
var VisualObject3D.x: Number
|
||||
get() = position?.x ?: 0f
|
||||
set(value) {
|
||||
position().x = value.toDouble()
|
||||
propertyInvalidated(VisualObject3D.xPos)
|
||||
}
|
||||
|
||||
var VisualObject3D.y: Number
|
||||
get() = position?.y ?: 0f
|
||||
set(value) {
|
||||
position().y = value.toDouble()
|
||||
propertyInvalidated(VisualObject3D.yPos)
|
||||
}
|
||||
|
||||
var VisualObject3D.z: Number
|
||||
get() = position?.z ?: 0f
|
||||
set(value) {
|
||||
position().z = value.toDouble()
|
||||
propertyInvalidated(VisualObject3D.zPos)
|
||||
}
|
||||
|
||||
private fun VisualObject3D.rotation(): Point3D =
|
||||
rotation ?: Point3D(0.0, 0.0, 0.0).also { rotation = it }
|
||||
|
||||
var VisualObject3D.rotationX: Number
|
||||
get() = rotation?.x ?: 0f
|
||||
set(value) {
|
||||
rotation().x = value.toDouble()
|
||||
propertyInvalidated(VisualObject3D.xRotation)
|
||||
}
|
||||
|
||||
var VisualObject3D.rotationY: Number
|
||||
get() = rotation?.y ?: 0f
|
||||
set(value) {
|
||||
rotation().y = value.toDouble()
|
||||
propertyInvalidated(VisualObject3D.yRotation)
|
||||
}
|
||||
|
||||
var VisualObject3D.rotationZ: Number
|
||||
get() = rotation?.z ?: 0f
|
||||
set(value) {
|
||||
rotation().z = value.toDouble()
|
||||
propertyInvalidated(VisualObject3D.zRotation)
|
||||
}
|
||||
|
||||
private fun VisualObject3D.scale(): Point3D =
|
||||
scale ?: Point3D(1.0, 1.0, 1.0).also { scale = it }
|
||||
|
||||
var VisualObject3D.scaleX: Number
|
||||
get() = scale?.x ?: 1f
|
||||
set(value) {
|
||||
scale().x = value.toDouble()
|
||||
propertyInvalidated(VisualObject3D.xScale)
|
||||
}
|
||||
|
||||
var VisualObject3D.scaleY: Number
|
||||
get() = scale?.y ?: 1f
|
||||
set(value) {
|
||||
scale().y = value.toDouble()
|
||||
propertyInvalidated(VisualObject3D.yScale)
|
||||
}
|
||||
|
||||
var VisualObject3D.scaleZ: Number
|
||||
get() = scale?.z ?: 1f
|
||||
set(value) {
|
||||
scale().z = value.toDouble()
|
||||
propertyInvalidated(VisualObject3D.zScale)
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
package hep.dataforge.vis.spatial.three
|
||||
|
||||
import hep.dataforge.context.Global
|
||||
import hep.dataforge.vis.spatial.VisualObject3D
|
||||
import hep.dataforge.vis.spatial.specifications.Canvas
|
||||
import kotlinx.html.id
|
||||
import org.w3c.dom.HTMLElement
|
||||
import react.RBuilder
|
||||
import react.RComponent
|
||||
import react.RProps
|
||||
import react.RState
|
||||
import react.dom.div
|
||||
import kotlin.browser.document
|
||||
import kotlin.dom.clear
|
||||
|
||||
interface ThreeCanvasProps : RProps {
|
||||
var obj: VisualObject3D
|
||||
var canvasId: String
|
||||
var options: Canvas
|
||||
}
|
||||
|
||||
class ThreeCanvasComponent : RComponent<ThreeCanvasProps, RState>() {
|
||||
|
||||
private val three: ThreePlugin = Global.plugins.fetch(ThreePlugin)
|
||||
|
||||
override fun componentDidMount() {
|
||||
val element = document.getElementById(props.canvasId) as? HTMLElement
|
||||
?: error("Element with id 'canvas' not found on page")
|
||||
val output = three.output(element, props.options)
|
||||
output.render(props.obj)
|
||||
}
|
||||
|
||||
override fun componentWillUnmount() {
|
||||
val element = document.getElementById(props.canvasId) as? HTMLElement
|
||||
?: error("Element with id 'canvas' not found on page")
|
||||
element.clear()
|
||||
}
|
||||
|
||||
override fun RBuilder.render() {
|
||||
div {
|
||||
attrs {
|
||||
id = props.canvasId
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun RBuilder.threeCanvas(object3D: VisualObject3D, id: String = "threeCanvas", options: Canvas.() -> Unit = {}) {
|
||||
child(ThreeCanvasComponent::class) {
|
||||
attrs {
|
||||
this.obj = object3D
|
||||
this.canvasId = id
|
||||
this.options = Canvas.invoke(options)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
package hep.dataforge.vis.spatial.three
|
||||
|
||||
import hep.dataforge.js.jsObject
|
||||
import hep.dataforge.meta.MetaItem
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.vis.spatial.Label3D
|
||||
import hep.dataforge.vis.spatial.three.ThreeMaterials.getMaterial
|
||||
import info.laht.threekt.core.Object3D
|
||||
import info.laht.threekt.geometries.TextBufferGeometry
|
||||
import info.laht.threekt.objects.Mesh
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
object ThreeLabelFactory : ThreeFactory<Label3D> {
|
||||
override val type: KClass<in Label3D> get() = Label3D::class
|
||||
|
||||
override fun invoke(obj: Label3D): Object3D {
|
||||
val textGeo = TextBufferGeometry( obj.text, jsObject {
|
||||
font = obj.fontFamily
|
||||
size = 20
|
||||
height = 1
|
||||
curveSegments = 1
|
||||
} )
|
||||
return Mesh(textGeo, getMaterial(obj)).apply {
|
||||
updatePosition(obj)
|
||||
obj.onPropertyChange(this@ThreeLabelFactory){ _: Name, _: MetaItem<*>?, _: MetaItem<*>? ->
|
||||
//TODO
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
15
demo/gdml/README.md
Normal file
@ -0,0 +1,15 @@
|
||||
|
||||
### GDML Example
|
||||
|
||||
Visualization example for geometry defined as GDML file.
|
||||
|
||||
##### Building project
|
||||
|
||||
To build the app, run `demo/gdml/Tasks/distribution/jsBrowserDistribution` Gradle task, then open
|
||||
`demo/gdml/build/distribuions/gdml-js-0.1.3-dev/index.html` file in your browser, and
|
||||
drag-and-drop GDML file to the window to see visualization. For an example file, you can use
|
||||
`demo/gdml/src/jsMain/resources/cubes.gdml`.
|
||||
|
||||
##### Example view:
|
||||
|
||||
![](../../doc/resources/gdml-demo.png)
|
@ -1,6 +1,6 @@
|
||||
import scientifik.DependencyConfiguration
|
||||
import scientifik.FXModule
|
||||
import scientifik.fx
|
||||
import scientifik.useFx
|
||||
|
||||
plugins {
|
||||
id("scientifik.mpp")
|
||||
@ -8,7 +8,7 @@ plugins {
|
||||
}
|
||||
|
||||
val fxVersion: String by rootProject.extra
|
||||
fx(FXModule.CONTROLS, version = fxVersion, configuration = DependencyConfiguration.IMPLEMENTATION)
|
||||
useFx(FXModule.CONTROLS, version = fxVersion, configuration = DependencyConfiguration.IMPLEMENTATION)
|
||||
|
||||
kotlin {
|
||||
|
||||
@ -17,23 +17,31 @@ kotlin {
|
||||
}
|
||||
|
||||
js {
|
||||
browser {
|
||||
webpackTask {
|
||||
//sourceMaps = false
|
||||
}
|
||||
}
|
||||
useCommonJs()
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api(project(":dataforge-vis-spatial"))
|
||||
api(project(":dataforge-vis-spatial-gdml"))
|
||||
implementation(project(":visionforge-solid"))
|
||||
implementation(project(":visionforge-gdml"))
|
||||
}
|
||||
}
|
||||
jsMain{
|
||||
dependencies {
|
||||
implementation(project(":ui:bootstrap"))
|
||||
implementation(npm("react-file-drop", "3.0.6"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
application {
|
||||
mainClassName = "hep.dataforge.vis.spatial.gdml.demo.GDMLDemoAppKt"
|
||||
mainClassName = "hep.dataforge.vision.gdml.demo.GDMLDemoAppKt"
|
||||
}
|
||||
|
||||
val convertGdmlToJson by tasks.creating(JavaExec::class) {
|
||||
group = "application"
|
||||
classpath = sourceSets["main"].runtimeClasspath
|
||||
main = "hep.dataforge.vis.spatial.gdml.demo.SaveToJsonKt"
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package hep.dataforge.vis.spatial.gdml.demo
|
||||
package hep.dataforge.vision.gdml.demo
|
||||
|
||||
import scientifik.gdml.*
|
||||
|
||||
@ -12,7 +12,7 @@ fun cubes(): GDML = GDML {
|
||||
val segment = solids.tube("segment", 20, 5.0) {
|
||||
rmin = 17
|
||||
deltaphi = 60
|
||||
aunit = DEG
|
||||
aunit = AUnit.DEG.title
|
||||
}
|
||||
val worldBox = solids.box("LargeBox", 200, 200, 200)
|
||||
val smallBox = solids.box("smallBox", 30, 30, 30)
|
||||
@ -24,7 +24,7 @@ fun cubes(): GDML = GDML {
|
||||
positionref = center.ref()
|
||||
rotation {
|
||||
z = 60 * i
|
||||
unit = DEG
|
||||
unit = AUnit.DEG.title
|
||||
}
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package hep.dataforge.vis.spatial.gdml
|
||||
|
||||
import hep.dataforge.meta.setProperty
|
||||
import hep.dataforge.meta.string
|
||||
import hep.dataforge.names.toName
|
||||
import hep.dataforge.values.asValue
|
||||
import hep.dataforge.vis.spatial.Material3D
|
||||
import hep.dataforge.vis.spatial.gdml.demo.cubes
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class GDMLVisualTest {
|
||||
@Test
|
||||
fun testPrototypeProperty() {
|
||||
val gdml = cubes()
|
||||
val visual = gdml.toVisual()
|
||||
visual["composite000.segment0".toName()]?.setProperty(Material3D.MATERIAL_COLOR_KEY, "red".asValue())
|
||||
assertEquals("red", visual["composite000.segment0".toName()]?.getProperty(Material3D.MATERIAL_COLOR_KEY).string)
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package hep.dataforge.vision.gdml
|
||||
|
||||
import hep.dataforge.meta.setItem
|
||||
import hep.dataforge.meta.string
|
||||
import hep.dataforge.names.toName
|
||||
import hep.dataforge.values.asValue
|
||||
import hep.dataforge.vision.gdml.demo.cubes
|
||||
import hep.dataforge.vision.solid.SolidMaterial
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class GDMLVisualTest {
|
||||
@Test
|
||||
fun testPrototypeProperty() {
|
||||
val gdml = cubes()
|
||||
val visual = gdml.toVision()
|
||||
visual["composite000.segment0".toName()]?.setItem(SolidMaterial.MATERIAL_COLOR_KEY, "red".asValue())
|
||||
assertEquals("red", visual["composite000.segment0".toName()]?.getItem(SolidMaterial.MATERIAL_COLOR_KEY).string)
|
||||
}
|
||||
}
|
40
demo/gdml/src/jsMain/kotlin/drop/FileDrop.kt
Normal file
@ -0,0 +1,40 @@
|
||||
@file:JsModule("react-file-drop")
|
||||
@file:JsNonModule
|
||||
|
||||
package drop
|
||||
|
||||
import org.w3c.dom.DragEvent
|
||||
import org.w3c.files.FileList
|
||||
import react.*
|
||||
|
||||
external enum class DropEffects {
|
||||
copy,
|
||||
move,
|
||||
link,
|
||||
none
|
||||
}
|
||||
|
||||
external interface FileDropProps: RProps {
|
||||
var className: String?
|
||||
var targetClassName: String?
|
||||
var draggingOverFrameClassName: String?
|
||||
var draggingOverTargetClassName: String?
|
||||
|
||||
// var frame?: Exclude<HTMLElementTagNameMap[keyof HTMLElementTagNameMap], HTMLElement> | HTMLDocument;
|
||||
var onFrameDragEnter: ((event: DragEvent) -> Unit)?
|
||||
var onFrameDragLeave: ((event: DragEvent) -> Unit)?
|
||||
var onFrameDrop: ((event: DragEvent) -> Unit)?
|
||||
// var onDragOver: ReactDragEventHandler<HTMLDivElement>?
|
||||
// var onDragLeave: ReactDragEventHandler<HTMLDivElement>?
|
||||
var onDrop: ((files: FileList?, event: dynamic) -> Unit)?//event:DragEvent<HTMLDivElement>)
|
||||
var dropEffect: DropEffects?
|
||||
}
|
||||
|
||||
external interface FileDropState: RState {
|
||||
var draggingOverFrame: Boolean
|
||||
var draggingOverTarget: Boolean
|
||||
}
|
||||
|
||||
external class FileDrop : Component<FileDropProps, FileDropState> {
|
||||
override fun render(): dynamic
|
||||
}
|
@ -1,223 +0,0 @@
|
||||
package hep.dataforge.vis.spatial.gdml.demo
|
||||
|
||||
import hep.dataforge.context.Global
|
||||
import hep.dataforge.js.Application
|
||||
import hep.dataforge.js.startApplication
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.isEmpty
|
||||
import hep.dataforge.vis.VisualGroup
|
||||
import hep.dataforge.vis.VisualObject
|
||||
import hep.dataforge.vis.editor.objectTree
|
||||
import hep.dataforge.vis.editor.visualPropertyEditor
|
||||
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_COLOR_KEY
|
||||
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_OPACITY_KEY
|
||||
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_WIREFRAME_KEY
|
||||
import hep.dataforge.vis.spatial.VisualGroup3D
|
||||
import hep.dataforge.vis.spatial.VisualObject3D
|
||||
import hep.dataforge.vis.spatial.VisualObject3D.Companion.VISIBLE_KEY
|
||||
import hep.dataforge.vis.spatial.gdml.GDMLTransformer
|
||||
import hep.dataforge.vis.spatial.gdml.LUnit
|
||||
import hep.dataforge.vis.spatial.gdml.toVisual
|
||||
import hep.dataforge.vis.spatial.three.ThreePlugin
|
||||
import hep.dataforge.vis.spatial.three.displayCanvasControls
|
||||
import hep.dataforge.vis.spatial.three.output
|
||||
import org.w3c.dom.*
|
||||
import org.w3c.files.FileList
|
||||
import org.w3c.files.FileReader
|
||||
import org.w3c.files.get
|
||||
import scientifik.gdml.GDML
|
||||
import kotlin.browser.document
|
||||
import kotlin.browser.window
|
||||
import kotlin.dom.clear
|
||||
|
||||
private class GDMLDemoApp : Application {
|
||||
/**
|
||||
* Handle mouse drag according to https://www.html5rocks.com/en/tutorials/file/dndfiles/
|
||||
*/
|
||||
private fun handleDragOver(event: DragEvent) {
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
event.dataTransfer?.dropEffect = "copy"
|
||||
}
|
||||
|
||||
/**
|
||||
* Load data from text file
|
||||
*/
|
||||
private fun loadData(event: DragEvent, block: (name: String, data: String) -> Unit) {
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
|
||||
val file = (event.dataTransfer?.files as FileList)[0]
|
||||
?: throw RuntimeException("Failed to load file")
|
||||
|
||||
FileReader().apply {
|
||||
onload = {
|
||||
val string = result as String
|
||||
block(file.name, string)
|
||||
}
|
||||
readAsText(file)
|
||||
}
|
||||
}
|
||||
|
||||
private fun spinner(show: Boolean) {
|
||||
// if( show){
|
||||
//
|
||||
// val style = if (show) {
|
||||
// "display:block;"
|
||||
// } else {
|
||||
// "display:none;"
|
||||
// }
|
||||
// document.getElementById("canvas")?.append {
|
||||
//
|
||||
// }
|
||||
}
|
||||
|
||||
private fun message(message: String?) {
|
||||
console.log(message)
|
||||
// document.getElementById("messages")?.let { element ->
|
||||
// if (message == null) {
|
||||
// element.clear()
|
||||
// } else {
|
||||
// element.append {
|
||||
// p {
|
||||
// +message
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
private val gdmlConfiguration: GDMLTransformer.() -> Unit = {
|
||||
lUnit = LUnit.CM
|
||||
volumeAction = { volume ->
|
||||
when {
|
||||
volume.name.startsWith("ecal01lay") -> GDMLTransformer.Action.REJECT
|
||||
volume.name.startsWith("UPBL") -> GDMLTransformer.Action.REJECT
|
||||
volume.name.startsWith("USCL") -> GDMLTransformer.Action.REJECT
|
||||
volume.name.startsWith("VPBL") -> GDMLTransformer.Action.REJECT
|
||||
volume.name.startsWith("VSCL") -> GDMLTransformer.Action.REJECT
|
||||
else -> GDMLTransformer.Action.CACHE
|
||||
}
|
||||
}
|
||||
|
||||
solidConfiguration = { parent, solid ->
|
||||
if (
|
||||
solid.name.startsWith("Yoke")
|
||||
|| solid.name.startsWith("Pole")
|
||||
|| parent.physVolumes.isNotEmpty()
|
||||
) {
|
||||
useStyle("opaque") {
|
||||
MATERIAL_OPACITY_KEY put 0.3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun start(state: Map<String, Any>) {
|
||||
|
||||
val context = Global.context("demo") {}
|
||||
val three = context.plugins.load(ThreePlugin)
|
||||
//val url = URL("https://drive.google.com/open?id=1w5e7fILMN83JGgB8WANJUYm8OW2s0WVO")
|
||||
|
||||
val canvasElement = document.getElementById("canvas") ?: error("Element with id 'canvas' not found on page")
|
||||
val configElement = document.getElementById("config") ?: error("Element with id 'layers' not found on page")
|
||||
val treeElement = document.getElementById("tree") ?: error("Element with id 'tree' not found on page")
|
||||
val editorElement = document.getElementById("editor") ?: error("Element with id 'editor' not found on page")
|
||||
canvasElement.clear()
|
||||
|
||||
val action: (name: String, data: String) -> Unit = { name, data ->
|
||||
canvasElement.clear()
|
||||
spinner(true)
|
||||
val visual: VisualObject3D = when {
|
||||
name.endsWith(".gdml") || name.endsWith(".xml") -> {
|
||||
message("Loading GDML")
|
||||
val gdml = GDML.format.parse(GDML.serializer(), data)
|
||||
message("Converting GDML into DF-VIS format")
|
||||
gdml.toVisual(gdmlConfiguration)
|
||||
}
|
||||
name.endsWith(".json") -> VisualGroup3D.parseJson(data)
|
||||
else -> {
|
||||
window.alert("File extension is not recognized: $name")
|
||||
error("File extension is not recognized: $name")
|
||||
}
|
||||
}
|
||||
|
||||
//Optimize tree
|
||||
//(visual as? VisualGroup3D)?.transformInPlace(UnRef, RemoveSingleChild)
|
||||
|
||||
message("Rendering")
|
||||
|
||||
//output.camera.layers.enable(1)
|
||||
val canvas = three.output(canvasElement as HTMLElement)
|
||||
|
||||
canvas.camera.layers.set(0)
|
||||
configElement.displayCanvasControls(canvas)
|
||||
//tree.visualObjectTree(visual, editor::propertyEditor)
|
||||
fun selectElement(name: Name) {
|
||||
val child: VisualObject = when {
|
||||
name.isEmpty() -> visual
|
||||
visual is VisualGroup -> visual[name] ?: return
|
||||
else -> return
|
||||
}
|
||||
|
||||
editorElement.visualPropertyEditor(name, child) {
|
||||
VISIBLE_KEY put true
|
||||
if (child is VisualObject3D) {
|
||||
MATERIAL_COLOR_KEY put "#ffffff"
|
||||
MATERIAL_OPACITY_KEY put 1.0
|
||||
MATERIAL_WIREFRAME_KEY put false
|
||||
}
|
||||
}
|
||||
// editorElement.displayPropertyEditor(name, child) { item ->
|
||||
// //val descriptorMeta = Material3D.descriptor
|
||||
//
|
||||
// val properties = item.allProperties()
|
||||
// val bottom = Meta {
|
||||
// VISIBLE_KEY put (item.visible ?: true)
|
||||
// if (item is VisualObject3D) {
|
||||
// MATERIAL_COLOR_KEY put "#ffffff"
|
||||
// MATERIAL_OPACITY_KEY put 1.0
|
||||
// MATERIAL_WIREFRAME_KEY put false
|
||||
// }
|
||||
// }
|
||||
// properties.withBottom(bottom)
|
||||
// }
|
||||
}
|
||||
|
||||
// canvas.clickListener = ::selectElement
|
||||
|
||||
//tree.visualObjectTree(visual, editor::propertyEditor)
|
||||
treeElement.objectTree(visual) { treeName ->
|
||||
selectElement(treeName)
|
||||
canvas.highlight(treeName)
|
||||
}
|
||||
canvas.render(visual)
|
||||
message(null)
|
||||
spinner(false)
|
||||
}
|
||||
|
||||
(document.getElementById("drop_zone") as? HTMLDivElement)?.apply {
|
||||
addEventListener("dragover", { handleDragOver(it as DragEvent) }, false)
|
||||
addEventListener("drop", { loadData(it as DragEvent, action) }, false)
|
||||
}
|
||||
(document.getElementById("file_load_button") as? HTMLInputElement)?.apply {
|
||||
addEventListener("change", {
|
||||
(it.target as HTMLInputElement).files?.asList()?.first()?.let { file ->
|
||||
FileReader().apply {
|
||||
onload = {
|
||||
val string = result as String
|
||||
action(file.name, string)
|
||||
}
|
||||
readAsText(file)
|
||||
}
|
||||
}
|
||||
}, false)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fun main() {
|
||||
startApplication(::GDMLDemoApp)
|
||||
}
|
@ -0,0 +1,154 @@
|
||||
package hep.dataforge.vision.gdml.demo
|
||||
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.isEmpty
|
||||
import hep.dataforge.vision.Vision
|
||||
import hep.dataforge.vision.VisionGroup
|
||||
import hep.dataforge.vision.bootstrap.*
|
||||
import hep.dataforge.vision.gdml.toVision
|
||||
import hep.dataforge.vision.react.component
|
||||
import hep.dataforge.vision.react.configEditor
|
||||
import hep.dataforge.vision.react.flexColumn
|
||||
import hep.dataforge.vision.react.state
|
||||
import hep.dataforge.vision.solid.Solid
|
||||
import hep.dataforge.vision.solid.SolidGroup
|
||||
import hep.dataforge.vision.solid.specifications.Camera
|
||||
import hep.dataforge.vision.solid.specifications.Canvas3DOptions
|
||||
import hep.dataforge.vision.solid.three.ThreeCanvas
|
||||
import hep.dataforge.vision.solid.three.ThreeCanvasComponent
|
||||
import hep.dataforge.vision.solid.three.canvasControls
|
||||
import kotlinx.css.FlexBasis
|
||||
import kotlinx.css.Overflow
|
||||
import kotlinx.css.flex
|
||||
import kotlinx.css.overflow
|
||||
import org.w3c.files.FileReader
|
||||
import org.w3c.files.get
|
||||
import react.RProps
|
||||
import react.dom.h1
|
||||
import scientifik.gdml.GDML
|
||||
import scientifik.gdml.parse
|
||||
import styled.css
|
||||
import styled.styledDiv
|
||||
import kotlin.browser.window
|
||||
import kotlin.math.PI
|
||||
|
||||
interface GDMLAppProps : RProps {
|
||||
var context: Context
|
||||
var rootObject: Vision?
|
||||
var selected: Name?
|
||||
}
|
||||
|
||||
private val canvasConfig = Canvas3DOptions {
|
||||
camera = Camera {
|
||||
distance = 2100.0
|
||||
latitude = PI / 6
|
||||
azimuth = PI + PI / 6
|
||||
}
|
||||
}
|
||||
|
||||
val GDMLApp = component<GDMLAppProps> { props ->
|
||||
var selected by state { props.selected }
|
||||
var canvas: ThreeCanvas? by state { null }
|
||||
var visual: Vision? by state { props.rootObject }
|
||||
|
||||
val select: (Name?) -> Unit = {
|
||||
selected = it
|
||||
}
|
||||
|
||||
fun loadData(name: String, data: String) {
|
||||
visual = when {
|
||||
name.endsWith(".gdml") || name.endsWith(".xml") -> {
|
||||
val gdml = GDML.parse(data)
|
||||
gdml.toVision(gdmlConfiguration)
|
||||
}
|
||||
name.endsWith(".json") -> SolidGroup.parseJson(data)
|
||||
else -> {
|
||||
window.alert("File extension is not recognized: $name")
|
||||
error("File extension is not recognized: $name")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flexColumn {
|
||||
css {
|
||||
flex(1.0, 1.0, FlexBasis.auto)
|
||||
}
|
||||
h1 { +"GDML/JSON loader demo" }
|
||||
styledDiv {
|
||||
css {
|
||||
classes.add("row")
|
||||
classes.add("p-1")
|
||||
overflow = Overflow.auto
|
||||
}
|
||||
gridColumn(3, maxSize= GridMaxSize.XL, classes = "order-2 order-xl-1") {
|
||||
card("Load data") {
|
||||
fileDrop("(drag file here)") { files ->
|
||||
val file = files?.get(0)
|
||||
if (file != null) {
|
||||
|
||||
FileReader().apply {
|
||||
onload = {
|
||||
val string = result as String
|
||||
loadData(file.name, string)
|
||||
}
|
||||
readAsText(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//tree
|
||||
card("Object tree", "overflow-auto") {
|
||||
visual?.let {
|
||||
objectTree(it, selected, select)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gridColumn(6, maxSize= GridMaxSize.XL, classes = "order-1 order-xl-2") {
|
||||
//canvas
|
||||
(visual as? Solid)?.let { visual3D ->
|
||||
child(ThreeCanvasComponent::class) {
|
||||
attrs {
|
||||
this.context = props.context
|
||||
this.obj = visual3D
|
||||
this.selected = selected
|
||||
this.clickCallback = select
|
||||
this.canvasCallback = {
|
||||
canvas = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
gridColumn(3, maxSize= GridMaxSize.XL, classes = "order-3") {
|
||||
container {
|
||||
//settings
|
||||
canvas?.let {
|
||||
card("Canvas configuration") {
|
||||
canvasControls(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
container {
|
||||
namecrumbs(selected, "World") { selected = it }
|
||||
}
|
||||
container {
|
||||
//properties
|
||||
card("Properties") {
|
||||
selected.let { selected ->
|
||||
val selectedObject: Vision? = when {
|
||||
selected == null -> null
|
||||
selected.isEmpty() -> visual
|
||||
else -> (visual as? VisionGroup)?.get(selected)
|
||||
}
|
||||
if (selectedObject != null) {
|
||||
configEditor(selectedObject, default = selectedObject.getAllProperties(), key = selected)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
package hep.dataforge.vision.gdml.demo
|
||||
|
||||
import hep.dataforge.context.Global
|
||||
import hep.dataforge.js.Application
|
||||
import hep.dataforge.js.startApplication
|
||||
import hep.dataforge.vision.gdml.GDMLTransformer
|
||||
import hep.dataforge.vision.gdml.LUnit
|
||||
import hep.dataforge.vision.gdml.toVision
|
||||
import hep.dataforge.vision.solid.SolidMaterial.Companion.MATERIAL_OPACITY_KEY
|
||||
import kotlinx.css.*
|
||||
import react.child
|
||||
import react.dom.render
|
||||
import styled.injectGlobal
|
||||
import kotlin.browser.document
|
||||
|
||||
|
||||
val gdmlConfiguration: GDMLTransformer.() -> Unit = {
|
||||
lUnit = LUnit.CM
|
||||
volumeAction = { volume ->
|
||||
when {
|
||||
volume.name.startsWith("ecal01lay") -> GDMLTransformer.Action.REJECT
|
||||
volume.name.startsWith("UPBL") -> GDMLTransformer.Action.REJECT
|
||||
volume.name.startsWith("USCL") -> GDMLTransformer.Action.REJECT
|
||||
volume.name.startsWith("VPBL") -> GDMLTransformer.Action.REJECT
|
||||
volume.name.startsWith("VSCL") -> GDMLTransformer.Action.REJECT
|
||||
else -> GDMLTransformer.Action.CACHE
|
||||
}
|
||||
}
|
||||
|
||||
solidConfiguration = { parent, solid ->
|
||||
if (
|
||||
solid.name.startsWith("Yoke")
|
||||
|| solid.name.startsWith("Pole")
|
||||
|| parent.physVolumes.isNotEmpty()
|
||||
) {
|
||||
useStyle("opaque") {
|
||||
MATERIAL_OPACITY_KEY put 0.3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class GDMLDemoApp : Application {
|
||||
|
||||
override fun start(state: Map<String, Any>) {
|
||||
|
||||
injectGlobal {
|
||||
body {
|
||||
height = 100.pct
|
||||
width = 100.pct
|
||||
margin(0.px)
|
||||
padding(0.px)
|
||||
}
|
||||
}
|
||||
|
||||
val context = Global.context("demo") {}
|
||||
val element = document.getElementById("app") ?: error("Element with id 'app' not found on page")
|
||||
|
||||
render(element) {
|
||||
child(GDMLApp) {
|
||||
attrs {
|
||||
this.context = context
|
||||
this.rootObject = cubes().toVision(gdmlConfiguration)
|
||||
}
|
||||
}
|
||||
}
|
||||
// (document.getElementById("file_load_button") as? HTMLInputElement)?.apply {
|
||||
// addEventListener("change", {
|
||||
// (it.target as HTMLInputElement).files?.asList()?.first()?.let { file ->
|
||||
// FileReader().apply {
|
||||
// onload = {
|
||||
// val string = result as String
|
||||
// action(file.name, string)
|
||||
// }
|
||||
// readAsText(file)
|
||||
// }
|
||||
// }
|
||||
// }, false)
|
||||
// }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fun main() {
|
||||
startApplication(::GDMLDemoApp)
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package hep.dataforge.vision.gdml.demo
|
||||
|
||||
import drop.FileDrop
|
||||
import kotlinx.css.*
|
||||
import kotlinx.css.properties.border
|
||||
import org.w3c.files.FileList
|
||||
import react.RBuilder
|
||||
import styled.css
|
||||
import styled.styledDiv
|
||||
|
||||
//TODO move styles to inline
|
||||
|
||||
fun RBuilder.fileDrop(title: String, action: (files: FileList?) -> Unit) {
|
||||
styledDiv {
|
||||
css {
|
||||
border(style = BorderStyle.dashed, width = 1.px, color = Color.orange)
|
||||
alignContent = Align.center
|
||||
}
|
||||
|
||||
child(FileDrop::class) {
|
||||
attrs {
|
||||
onDrop = { files, _ ->
|
||||
console.info("loaded $files")
|
||||
action(files)
|
||||
}
|
||||
}
|
||||
+title
|
||||
}
|
||||
}
|
||||
}
|
62
demo/gdml/src/jsMain/resources/css/fileDrop.css
Normal file
@ -0,0 +1,62 @@
|
||||
.file-drop {
|
||||
/* relatively position the container bc the contents are absolute */
|
||||
position: relative;
|
||||
height: 100px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.file-drop > .file-drop-target {
|
||||
/* basic styles */
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border-radius: 2px;
|
||||
|
||||
/* horizontally and vertically center all content */
|
||||
display: flex;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
|
||||
flex-direction: column;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-box-direction: normal;
|
||||
-webkit-flex-direction: column;
|
||||
-ms-flex-direction: column;
|
||||
|
||||
align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-webkit-align-items: center;
|
||||
-ms-flex-align: center;
|
||||
|
||||
justify-content: center;
|
||||
-webkit-box-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
-ms-flex-pack: center;
|
||||
|
||||
align-content: center;
|
||||
-webkit-align-content: center;
|
||||
-ms-flex-line-pack: center;
|
||||
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.file-drop > .file-drop-target.file-drop-dragging-over-frame {
|
||||
/* overlay a black mask when dragging over the frame */
|
||||
border: none;
|
||||
background-color: rgba(0, 0, 0, 0.65);
|
||||
box-shadow: none;
|
||||
z-index: 50;
|
||||
opacity: 1;
|
||||
|
||||
/* typography */
|
||||
color: white;
|
||||
}
|
||||
|
||||
.file-drop > .file-drop-target.file-drop-dragging-over-target {
|
||||
/* turn stuff orange when we are dragging over the target */
|
||||
color: #ff6e40;
|
||||
box-shadow: 0 0 13px 3px #ff6e40;
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
.drop_zone {
|
||||
outline: 1px solid orange;
|
||||
}
|
||||
|
||||
.loader {
|
||||
border: 16px solid #f3f3f3; /* Light grey */
|
||||
border-top: 16px solid #3498db; /* Blue */
|
||||
border-radius: 50%;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
animation: spin 2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Remove default bullets */
|
||||
ul, .tree {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
/* Style the caret/arrow */
|
||||
.tree-caret {
|
||||
cursor: pointer;
|
||||
user-select: none; /* Prevent text selection */
|
||||
}
|
||||
|
||||
.objTree-label {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Create the caret/arrow with a unicode, and style it */
|
||||
.tree-caret::before {
|
||||
content: "\25B6";
|
||||
color: black;
|
||||
display: inline-block;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.objTree-leaf::before {
|
||||
content: "\25C6";
|
||||
color: black;
|
||||
display: inline-block;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
/* Rotate the caret/arrow icon when clicked on (using JavaScript) */
|
||||
.tree-caret-down::before {
|
||||
transform: rotate(90deg);
|
||||
}
|
@ -1,748 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="240"
|
||||
height="144"
|
||||
id="svg4136"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="jsoneditor-icons.svg">
|
||||
<title
|
||||
id="title6512">JSON Editor Icons</title>
|
||||
<metadata
|
||||
id="metadata4148">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title>JSON Editor Icons</dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs4146" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ff63ff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1026"
|
||||
id="namedview4144"
|
||||
showgrid="true"
|
||||
inkscape:zoom="4"
|
||||
inkscape:cx="13.229181"
|
||||
inkscape:cy="119.82429"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4136"
|
||||
showguides="false"
|
||||
borderlayer="false"
|
||||
inkscape:showpageshadow="true"
|
||||
showborder="true">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid4640"
|
||||
empspacing="24" />
|
||||
</sodipodi:namedview>
|
||||
<!-- Created with SVG-edit - http://svg-edit.googlecode.com/ -->
|
||||
<rect
|
||||
style="fill:#4c4c4c;fill-opacity:1;stroke:none;stroke-width:0"
|
||||
id="svg_1"
|
||||
height="16"
|
||||
width="16"
|
||||
y="4"
|
||||
x="4" />
|
||||
<rect
|
||||
id="svg_1-7"
|
||||
height="16"
|
||||
width="16"
|
||||
y="3.999995"
|
||||
x="28.000006"
|
||||
style="fill:#ec3f29;fill-opacity:0.94117647;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
style="fill:#4c4c4c;fill-opacity:1;stroke:none;stroke-width:0"
|
||||
x="52.000004"
|
||||
y="3.999995"
|
||||
width="16"
|
||||
height="16"
|
||||
id="rect4165" />
|
||||
<rect
|
||||
id="rect4175"
|
||||
height="16"
|
||||
width="16"
|
||||
y="3.9999852"
|
||||
x="172.00002"
|
||||
style="fill:#4c4c4c;fill-opacity:1;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
id="rect4175-3"
|
||||
height="16"
|
||||
width="16"
|
||||
y="3.999995"
|
||||
x="196"
|
||||
style="fill:#4c4c4c;fill-opacity:1;stroke:none;stroke-width:0" />
|
||||
<g
|
||||
id="g4299"
|
||||
style="stroke:none">
|
||||
<rect
|
||||
x="7.0000048"
|
||||
y="10.999998"
|
||||
width="9.9999924"
|
||||
height="1.9999986"
|
||||
id="svg_1-1"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
x="11.000005"
|
||||
y="7.0000114"
|
||||
width="1.9999955"
|
||||
height="9.9999838"
|
||||
id="svg_1-1-1"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0" />
|
||||
</g>
|
||||
<g
|
||||
id="g4299-3"
|
||||
transform="matrix(0.70710678,-0.70710678,0.70710678,0.70710678,19.029435,12.000001)"
|
||||
style="stroke:none">
|
||||
<rect
|
||||
x="7.0000048"
|
||||
y="10.999998"
|
||||
width="9.9999924"
|
||||
height="1.9999986"
|
||||
id="svg_1-1-0"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
x="11.000005"
|
||||
y="7.0000114"
|
||||
width="1.9999955"
|
||||
height="9.9999838"
|
||||
id="svg_1-1-1-9"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0" />
|
||||
</g>
|
||||
<rect
|
||||
id="svg_1-7-5"
|
||||
height="6.9999905"
|
||||
width="6.9999909"
|
||||
y="7.0000048"
|
||||
x="55.000004"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#4c4c4c;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
x="58"
|
||||
y="10.00001"
|
||||
width="6.9999909"
|
||||
height="6.9999905"
|
||||
id="rect4354" />
|
||||
<rect
|
||||
id="svg_1-7-5-7"
|
||||
height="6.9999905"
|
||||
width="6.9999909"
|
||||
y="10.000005"
|
||||
x="58.000004"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#3c80df;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.94117647" />
|
||||
<g
|
||||
id="g4378">
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0"
|
||||
x="198"
|
||||
y="10.999999"
|
||||
width="7.9999909"
|
||||
height="1.9999965"
|
||||
id="svg_1-7-5-3" />
|
||||
<rect
|
||||
id="rect4374"
|
||||
height="1.9999946"
|
||||
width="11.999995"
|
||||
y="7.0000005"
|
||||
x="198"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0" />
|
||||
<rect
|
||||
id="rect4376"
|
||||
height="1.9999995"
|
||||
width="3.9999928"
|
||||
y="14.999996"
|
||||
x="198"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0" />
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(1,0,0,-1,-23.999995,23.999995)"
|
||||
id="g4383">
|
||||
<rect
|
||||
id="rect4385"
|
||||
height="1.9999965"
|
||||
width="7.9999909"
|
||||
y="10.999999"
|
||||
x="198"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0"
|
||||
x="198"
|
||||
y="7.0000005"
|
||||
width="11.999995"
|
||||
height="1.9999946"
|
||||
id="rect4387" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0"
|
||||
x="198"
|
||||
y="14.999996"
|
||||
width="3.9999928"
|
||||
height="1.9999995"
|
||||
id="rect4389" />
|
||||
</g>
|
||||
<rect
|
||||
style="fill:#4c4c4c;fill-opacity:1;stroke:none"
|
||||
id="rect3754-4"
|
||||
width="16"
|
||||
height="16"
|
||||
x="76"
|
||||
y="3.9999199" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 85.10447,6.0157384 -0.0156,1.4063 c 3.02669,-0.2402 0.33008,3.6507996 2.48438,4.5780996 -2.18694,1.0938 0.49191,4.9069 -2.45313,4.5781 l -0.0156,1.4219 c 5.70828,0.559 1.03264,-5.1005 4.70313,-5.2656 l 0,-1.4063 c -3.61303,-0.027 1.11893,-5.7069996 -4.70313,-5.3124996 z"
|
||||
id="path4351"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccccccc" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 82.78125,5.9984384 0.0156,1.4063 c -3.02668,-0.2402 -0.33007,3.6506996 -2.48437,4.5780996 2.18694,1.0938 -0.49192,4.9069 2.45312,4.5781 l 0.0156,1.4219 c -5.70827,0.559 -1.03263,-5.1004 -4.70312,-5.2656 l 0,-1.4063 c 3.61303,-0.027 -1.11894,-5.7070996 4.70312,-5.3124996 z"
|
||||
id="path4351-9"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccccccc" />
|
||||
<rect
|
||||
style="fill:#4c4c4c;fill-opacity:1;stroke:none"
|
||||
id="rect3754-25"
|
||||
width="16"
|
||||
height="16"
|
||||
x="100"
|
||||
y="3.9999199" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none"
|
||||
d="m 103.719,5.6719384 0,12.7187996 3.03125,0 0,-1.5313 -1.34375,0 0,-9.6249996 1.375,0 0,-1.5625 z"
|
||||
id="path2987"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none"
|
||||
d="m 112.2185,5.6721984 0,12.7187996 -3.03125,0 0,-1.5313 1.34375,0 0,-9.6249996 -1.375,0 0,-1.5625 z"
|
||||
id="path2987-1"
|
||||
inkscape:connector-curvature="0" />
|
||||
<rect
|
||||
style="fill:#4c4c4c;fill-opacity:1;stroke:none"
|
||||
id="rect3754-73"
|
||||
width="16"
|
||||
height="16"
|
||||
x="124"
|
||||
y="3.9999199" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none"
|
||||
d="m 126.2824,17.602938 1.78957,0 1.14143,-2.8641 5.65364,0 1.14856,2.8641 1.76565,0 -4.78687,-11.1610996 -1.91903,0 z"
|
||||
id="path3780"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccccccc" />
|
||||
<path
|
||||
style="fill:#4c4c4c;fill-opacity:1;stroke:none"
|
||||
d="m 129.72704,13.478838 4.60852,0.01 -2.30426,-5.5497996 z"
|
||||
id="path3782"
|
||||
inkscape:connector-curvature="0" />
|
||||
<rect
|
||||
style="fill:#4c4c4c;fill-opacity:1;stroke:none"
|
||||
id="rect3754-35"
|
||||
width="16"
|
||||
height="16"
|
||||
x="148"
|
||||
y="3.9999199" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none"
|
||||
d="m 156.47655,5.8917384 0,2.1797 0.46093,2.3983996 1.82813,0 0.39844,-2.3983996 0,-2.1797 z"
|
||||
id="path5008-2"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccccc" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none"
|
||||
d="m 152.51561,5.8906384 0,2.1797 0.46094,2.3983996 1.82812,0 0.39844,-2.3983996 0,-2.1797 z"
|
||||
id="path5008-2-8"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccccc" />
|
||||
<rect
|
||||
id="svg_1-7-2"
|
||||
height="1.9999961"
|
||||
width="11.999996"
|
||||
y="64"
|
||||
x="54"
|
||||
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
id="svg_1-7-2-2"
|
||||
height="2.9999905"
|
||||
width="2.9999907"
|
||||
y="52"
|
||||
x="80.000008"
|
||||
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
|
||||
x="85.000008"
|
||||
y="52"
|
||||
width="2.9999907"
|
||||
height="2.9999905"
|
||||
id="rect4561" />
|
||||
<rect
|
||||
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
|
||||
x="80.000008"
|
||||
y="58"
|
||||
width="2.9999907"
|
||||
height="2.9999905"
|
||||
id="rect4563" />
|
||||
<rect
|
||||
id="rect4565"
|
||||
height="2.9999905"
|
||||
width="2.9999907"
|
||||
y="58"
|
||||
x="85.000008"
|
||||
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
id="rect4567"
|
||||
height="2.9999905"
|
||||
width="2.9999907"
|
||||
y="64"
|
||||
x="80.000008"
|
||||
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
|
||||
x="85.000008"
|
||||
y="64"
|
||||
width="2.9999907"
|
||||
height="2.9999905"
|
||||
id="rect4569" />
|
||||
<circle
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#4c4c4c;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
id="path4571"
|
||||
cx="110.06081"
|
||||
cy="57.939209"
|
||||
r="4.7438836" />
|
||||
<rect
|
||||
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
|
||||
x="116.64566"
|
||||
y="-31.79752"
|
||||
width="4.229713"
|
||||
height="6.4053884"
|
||||
id="rect4563-2"
|
||||
transform="matrix(0.70710678,0.70710678,-0.70710678,0.70710678,0,0)" />
|
||||
<path
|
||||
style="fill:#4c4c4c;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 125,56 138.77027,56.095 132,64 Z"
|
||||
id="path4613"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<path
|
||||
sodipodi:nodetypes="cccc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path4615"
|
||||
d="M 149,64 162.77027,63.905 156,56 Z"
|
||||
style="fill:#4c4c4c;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
|
||||
x="54"
|
||||
y="53"
|
||||
width="11.999996"
|
||||
height="1.9999961"
|
||||
id="rect4638" />
|
||||
<rect
|
||||
id="svg_1-7-2-24"
|
||||
height="1.9999957"
|
||||
width="12.99999"
|
||||
y="-56"
|
||||
x="53"
|
||||
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
|
||||
transform="matrix(0,1,-1,0,0,0)" />
|
||||
<rect
|
||||
transform="matrix(0,1,-1,0,0,0)"
|
||||
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
|
||||
x="53"
|
||||
y="-66"
|
||||
width="12.99999"
|
||||
height="1.9999957"
|
||||
id="rect4657" />
|
||||
<rect
|
||||
id="rect4659"
|
||||
height="0.99999291"
|
||||
width="11.999999"
|
||||
y="57"
|
||||
x="54"
|
||||
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
|
||||
x="54"
|
||||
y="88.000122"
|
||||
width="11.999996"
|
||||
height="1.9999961"
|
||||
id="rect4661" />
|
||||
<rect
|
||||
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
|
||||
x="80.000008"
|
||||
y="76.000122"
|
||||
width="2.9999907"
|
||||
height="2.9999905"
|
||||
id="rect4663" />
|
||||
<rect
|
||||
id="rect4665"
|
||||
height="2.9999905"
|
||||
width="2.9999907"
|
||||
y="76.000122"
|
||||
x="85.000008"
|
||||
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1" />
|
||||
<rect
|
||||
id="rect4667"
|
||||
height="2.9999905"
|
||||
width="2.9999907"
|
||||
y="82.000122"
|
||||
x="80.000008"
|
||||
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1" />
|
||||
<rect
|
||||
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
|
||||
x="85.000008"
|
||||
y="82.000122"
|
||||
width="2.9999907"
|
||||
height="2.9999905"
|
||||
id="rect4669" />
|
||||
<rect
|
||||
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
|
||||
x="80.000008"
|
||||
y="88.000122"
|
||||
width="2.9999907"
|
||||
height="2.9999905"
|
||||
id="rect4671" />
|
||||
<rect
|
||||
id="rect4673"
|
||||
height="2.9999905"
|
||||
width="2.9999907"
|
||||
y="88.000122"
|
||||
x="85.000008"
|
||||
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1" />
|
||||
<circle
|
||||
r="4.7438836"
|
||||
cy="81.939331"
|
||||
cx="110.06081"
|
||||
id="circle4675"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#d3d3d3;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
transform="matrix(0.70710678,0.70710678,-0.70710678,0.70710678,0,0)"
|
||||
id="rect4677"
|
||||
height="6.4053884"
|
||||
width="4.229713"
|
||||
y="-14.826816"
|
||||
x="133.6163"
|
||||
style="fill:#d3d3d3;fill-opacity:1;stroke:#d3d3d3;stroke-width:0;stroke-opacity:1" />
|
||||
<path
|
||||
sodipodi:nodetypes="cccc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path4679"
|
||||
d="m 125,80.000005 13.77027,0.09499 L 132,87.999992 Z"
|
||||
style="fill:#d3d3d3;fill-opacity:1;fill-rule:evenodd;stroke:#d3d3d3;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:#d3d3d3;fill-opacity:1;fill-rule:evenodd;stroke:#d3d3d3;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 149,88.0002 162.77027,87.9052 156,80.0002 Z"
|
||||
id="path4681"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<rect
|
||||
id="rect4683"
|
||||
height="1.9999961"
|
||||
width="11.999996"
|
||||
y="77.000122"
|
||||
x="54"
|
||||
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1" />
|
||||
<rect
|
||||
transform="matrix(0,1,-1,0,0,0)"
|
||||
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
|
||||
x="77.000122"
|
||||
y="-56"
|
||||
width="12.99999"
|
||||
height="1.9999957"
|
||||
id="rect4685" />
|
||||
<rect
|
||||
id="rect4687"
|
||||
height="1.9999957"
|
||||
width="12.99999"
|
||||
y="-66"
|
||||
x="77.000122"
|
||||
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
|
||||
transform="matrix(0,1,-1,0,0,0)" />
|
||||
<rect
|
||||
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
|
||||
x="54"
|
||||
y="81.000122"
|
||||
width="11.999999"
|
||||
height="0.99999291"
|
||||
id="rect4689" />
|
||||
<rect
|
||||
id="rect4761-1"
|
||||
height="1.9999945"
|
||||
width="15.99999"
|
||||
y="101"
|
||||
x="76.000008"
|
||||
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
id="rect4761-0"
|
||||
height="1.9999945"
|
||||
width="15.99999"
|
||||
y="105"
|
||||
x="76.000008"
|
||||
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
id="rect4761-7"
|
||||
height="1.9999945"
|
||||
width="9"
|
||||
y="109"
|
||||
x="76.000008"
|
||||
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
id="rect4761-1-1"
|
||||
height="1.9999945"
|
||||
width="12"
|
||||
y="125"
|
||||
x="76.000008"
|
||||
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
id="rect4761-1-1-4"
|
||||
height="1.9999945"
|
||||
width="10"
|
||||
y="137"
|
||||
x="76.000008"
|
||||
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
id="rect4761-1-1-4-4"
|
||||
height="1.9999945"
|
||||
width="10"
|
||||
y="129"
|
||||
x="82"
|
||||
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
id="rect4761-1-1-4-4-3"
|
||||
height="1.9999945"
|
||||
width="9"
|
||||
y="133"
|
||||
x="82"
|
||||
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.8;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.66157866;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 36.398438,100.0254 c -0.423362,-0.013 -0.846847,0.01 -1.265626,0.062 -1.656562,0.2196 -3.244567,0.9739 -4.507812,2.2266 L 29,100.5991 l -2.324219,7.7129 7.826172,-1.9062 -1.804687,-1.9063 c 1.597702,-1.5308 4.048706,-1.8453 5.984375,-0.7207 1.971162,1.1452 2.881954,3.3975 2.308593,5.5508 -0.573361,2.1533 -2.533865,3.6953 -4.830078,3.6953 l 0,3.0742 c 3.550756,0 6.710442,-2.4113 7.650391,-5.9414 0.939949,-3.5301 -0.618463,-7.2736 -3.710938,-9.0703 -1.159678,-0.6738 -2.431087,-1.0231 -3.701171,-1.0625 z"
|
||||
id="path4138" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.8;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.66157866;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 59.722656,99.9629 c -1.270084,0.039 -2.541493,0.3887 -3.701172,1.0625 -3.092475,1.7967 -4.650886,5.5402 -3.710937,9.0703 0.939949,3.5301 4.09768,5.9414 7.648437,5.9414 l 0,-3.0742 c -2.296214,0 -4.256717,-1.542 -4.830078,-3.6953 -0.573361,-2.1533 0.337432,-4.4056 2.308594,-5.5508 1.935731,-1.1246 4.38863,-0.8102 5.986326,0.7207 l -1.806638,1.9063 7.828128,1.9062 -2.32422,-7.7129 -1.62696,1.7168 c -1.26338,-1.2531 -2.848917,-2.0088 -4.505855,-2.2285 -0.418778,-0.055 -0.842263,-0.076 -1.265625,-0.062 z"
|
||||
id="path4138-1" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="opacity:0.8;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.96599996;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
d="m 10.5,100 0,2 -2.4999996,0 L 12,107 l 4,-5 -2.5,0 0,-2 -3,0 z"
|
||||
id="path3055-0-77" />
|
||||
<path
|
||||
style="opacity:0.8;fill:none;stroke:#ffffff;stroke-width:1.96599996;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 4.9850574,108.015 14.0298856,-0.03"
|
||||
id="path5244-5-0-5"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="opacity:0.8;fill:none;stroke:#ffffff;stroke-width:1.96599996;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 4.9849874,132.015 14.0298866,-0.03"
|
||||
id="path5244-5-0-5-8"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.4;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#4d4d4d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.66157866;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 36.398438,123.9629 c -0.423362,-0.013 -0.846847,0.01 -1.265626,0.062 -1.656562,0.2196 -3.244567,0.9739 -4.507812,2.2266 L 29,124.5366 l -2.324219,7.7129 7.826172,-1.9062 -1.804687,-1.9063 c 1.597702,-1.5308 4.048706,-1.8453 5.984375,-0.7207 1.971162,1.1453 2.881954,3.3975 2.308593,5.5508 -0.573361,2.1533 -2.533864,3.6953 -4.830078,3.6953 l 0,3.0742 c 3.550757,0 6.710442,-2.4093 7.650391,-5.9394 0.939949,-3.5301 -0.618463,-7.2756 -3.710938,-9.0723 -1.159678,-0.6737 -2.431087,-1.0231 -3.701171,-1.0625 z"
|
||||
id="path4138-12" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.4;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#4d4d4d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.66157866;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 59.722656,123.9629 c -1.270084,0.039 -2.541493,0.3888 -3.701172,1.0625 -3.092475,1.7967 -4.650886,5.5422 -3.710937,9.0723 0.939949,3.5301 4.09768,5.9394 7.648437,5.9394 l 0,-3.0742 c -2.296214,0 -4.256717,-1.542 -4.830078,-3.6953 -0.573361,-2.1533 0.337432,-4.4055 2.308594,-5.5508 1.935731,-1.1246 4.38863,-0.8102 5.986326,0.7207 l -1.806638,1.9063 7.828128,1.9062 -2.32422,-7.7129 -1.62696,1.7168 c -1.26338,-1.2531 -2.848917,-2.0088 -4.505855,-2.2285 -0.418778,-0.055 -0.842263,-0.076 -1.265625,-0.062 z"
|
||||
id="path4138-1-3" />
|
||||
<path
|
||||
id="path6191"
|
||||
d="m 10.5,116 0,-2 -2.4999996,0 L 12,109 l 4,5 -2.5,0 0,2 -3,0 z"
|
||||
style="opacity:0.8;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.96599996;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="opacity:0.8;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.96599996;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
d="m 10.5,129 0,-2 -2.4999996,0 L 12,122 l 4,5 -2.5,0 0,2 -3,0 z"
|
||||
id="path6193" />
|
||||
<path
|
||||
id="path6195"
|
||||
d="m 10.5,135 0,2 -2.4999996,0 L 12,142 l 4,-5 -2.5,0 0,-2 -3,0 z"
|
||||
style="opacity:0.8;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.96599996;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
sodipodi:type="star"
|
||||
style="fill:#4d4d4d;fill-opacity:0.90196078;stroke:#d3d3d3;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
id="path4500"
|
||||
sodipodi:sides="3"
|
||||
sodipodi:cx="11.55581"
|
||||
sodipodi:cy="60.073242"
|
||||
sodipodi:r1="5.1116104"
|
||||
sodipodi:r2="2.5558052"
|
||||
sodipodi:arg1="0"
|
||||
sodipodi:arg2="1.0471976"
|
||||
inkscape:flatsided="false"
|
||||
inkscape:rounded="0"
|
||||
inkscape:randomized="0"
|
||||
d="m 16.66742,60.073242 -3.833708,2.213392 -3.8337072,2.213393 0,-4.426785 0,-4.426784 3.8337082,2.213392 z"
|
||||
inkscape:transform-center-x="-1.2779026" />
|
||||
<path
|
||||
inkscape:transform-center-x="1.277902"
|
||||
d="m -31.500004,60.073242 -3.833708,2.213392 -3.833707,2.213393 0,-4.426785 0,-4.426784 3.833707,2.213392 z"
|
||||
inkscape:randomized="0"
|
||||
inkscape:rounded="0"
|
||||
inkscape:flatsided="false"
|
||||
sodipodi:arg2="1.0471976"
|
||||
sodipodi:arg1="0"
|
||||
sodipodi:r2="2.5558052"
|
||||
sodipodi:r1="5.1116104"
|
||||
sodipodi:cy="60.073242"
|
||||
sodipodi:cx="-36.611614"
|
||||
sodipodi:sides="3"
|
||||
id="path4502"
|
||||
style="fill:#4d4d4d;fill-opacity:0.90196078;stroke:#d3d3d3;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
sodipodi:type="star"
|
||||
transform="scale(-1,1)" />
|
||||
<path
|
||||
d="m 16.66742,60.073212 -3.833708,2.213392 -3.8337072,2.213392 0,-4.426784 0,-4.426785 3.8337082,2.213392 z"
|
||||
inkscape:randomized="0"
|
||||
inkscape:rounded="0"
|
||||
inkscape:flatsided="false"
|
||||
sodipodi:arg2="1.0471976"
|
||||
sodipodi:arg1="0"
|
||||
sodipodi:r2="2.5558052"
|
||||
sodipodi:r1="5.1116104"
|
||||
sodipodi:cy="60.073212"
|
||||
sodipodi:cx="11.55581"
|
||||
sodipodi:sides="3"
|
||||
id="path4504"
|
||||
style="fill:#4d4d4d;fill-opacity:0.90196078;stroke:#d3d3d3;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
sodipodi:type="star"
|
||||
transform="matrix(0,1,-1,0,72.0074,71.7877)"
|
||||
inkscape:transform-center-y="1.2779029" />
|
||||
<path
|
||||
inkscape:transform-center-y="-1.2779026"
|
||||
transform="matrix(0,-1,-1,0,96,96)"
|
||||
sodipodi:type="star"
|
||||
style="fill:#4d4d4d;fill-opacity:0.90196078;stroke:#d3d3d3;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
id="path4506"
|
||||
sodipodi:sides="3"
|
||||
sodipodi:cx="11.55581"
|
||||
sodipodi:cy="60.073212"
|
||||
sodipodi:r1="5.1116104"
|
||||
sodipodi:r2="2.5558052"
|
||||
sodipodi:arg1="0"
|
||||
sodipodi:arg2="1.0471976"
|
||||
inkscape:flatsided="false"
|
||||
inkscape:rounded="0"
|
||||
inkscape:randomized="0"
|
||||
d="m 16.66742,60.073212 -3.833708,2.213392 -3.8337072,2.213392 0,-4.426784 0,-4.426785 3.8337082,2.213392 z" />
|
||||
<path
|
||||
sodipodi:nodetypes="cccc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path4615-5"
|
||||
d="m 171.82574,65.174193 16.34854,0 -8.17427,-13.348454 z"
|
||||
style="fill:#fbb917;fill-opacity:1;fill-rule:evenodd;stroke:#fbb917;stroke-width:1.65161395;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 179,55 0,6 2,0 0,-6"
|
||||
id="path4300"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<path
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 179,62 0,2 2,0 0,-2"
|
||||
id="path4300-6"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:0.8;fill-rule:evenodd;stroke:#ffffff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:0.8"
|
||||
d="M 99.994369,113.0221 102,114.98353 l 7,-6.9558 3,0.97227 2,-1 1,-2 0,-3 -3,3 -3,-3 3,-3 -3,0 -2,1 -1,2 0.99437,3.0221 z"
|
||||
id="path4268"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccccccccccccc" />
|
||||
<rect
|
||||
id="rect4175-3-5"
|
||||
height="16"
|
||||
width="16"
|
||||
y="4"
|
||||
x="220"
|
||||
style="fill:#4c4c4c;fill-opacity:1;stroke:none;stroke-width:0" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 234,6 0,2 -5,5 0,5 -2,0 0,-5 -5,-5 0,-2"
|
||||
id="path3546"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccccccc" />
|
||||
<g
|
||||
transform="matrix(1.3333328,0,0,-1.5999992,-139.9999,127.19999)"
|
||||
id="g4383-6">
|
||||
<rect
|
||||
id="rect4385-2"
|
||||
height="1.2499905"
|
||||
width="5.9999924"
|
||||
y="12.625005"
|
||||
x="198.00002"
|
||||
style="fill:#ffffff;fill-opacity:0.8;stroke:#000000;stroke-width:0" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:0.8;stroke:#000000;stroke-width:0"
|
||||
x="198.00002"
|
||||
y="15.125007"
|
||||
width="7.4999928"
|
||||
height="1.2499949"
|
||||
id="rect4387-9" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:0.8;stroke:#000000;stroke-width:0"
|
||||
x="198.00002"
|
||||
y="7.6250024"
|
||||
width="2.9999909"
|
||||
height="1.2499905"
|
||||
id="rect4389-1-0" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:0.8;stroke:#000000;stroke-width:0"
|
||||
x="198.00002"
|
||||
y="10.125004"
|
||||
width="4.4999919"
|
||||
height="1.2499905"
|
||||
id="rect4389-1-9" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:0.8;fill-rule:evenodd;stroke:none;stroke-width:0.68465352px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 207.00001,16.375004 0,-5.625005 -2.25,0 3,-3.1250014 3,3.1250014 -2.25,0 0,5.625005 -1.5,0"
|
||||
id="path4402"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccccccc" />
|
||||
</g>
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:0.8;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 164,100 0,3 -6,6 0,7 -4,0 0,-7 -6,-6 0,-3"
|
||||
id="path3546-2-2"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccccccc" />
|
||||
<rect
|
||||
style="fill:#4c4c4c;fill-opacity:1;stroke:none;stroke-width:0"
|
||||
id="svg_1-3"
|
||||
height="16"
|
||||
width="16"
|
||||
y="28"
|
||||
x="4" />
|
||||
<path
|
||||
sodipodi:nodetypes="ccccccccc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path4402-5-7"
|
||||
d="m 15,41 0,-7 -4,0 0,3 -5,-4 5,-4 0,3 6,0 0,9"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.68465352px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
</svg>
|
Before Width: | Height: | Size: 31 KiB |
@ -4,45 +4,14 @@
|
||||
<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>
|
||||
<script type="text/javascript" src="main.bundle.js"></script>
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="css/jsoneditor.min.css">
|
||||
<link rel="stylesheet" href="css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="css/main.css">
|
||||
<link rel="stylesheet" href="css/fileDrop.css">
|
||||
<script type="text/javascript" src="main.bundle.js"></script>
|
||||
<script type="text/javascript" src ="js/jquery-3.4.1.min.js"></script>
|
||||
<script type="text/javascript" src ="js/bootstrap.bundle.min.js"></script>
|
||||
</head>
|
||||
<body class="testApp">
|
||||
|
||||
<div class="container">
|
||||
<h1>GDML demo</h1>
|
||||
</div>
|
||||
|
||||
<div class="container-fluid h-100">
|
||||
<div class="row h-100">
|
||||
<div class="col-lg-3">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<a>Load file:</a>
|
||||
<input type="file" id="file_load_button" accept=".gdml, application/xml, application/json"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-body" id="drop_zone">
|
||||
Load data
|
||||
<br/>
|
||||
(drag file here)
|
||||
</div>
|
||||
</div>
|
||||
<div class="overflow-auto" id="tree"></div>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div id="canvas"></div>
|
||||
</div>
|
||||
<div class="col-lg-3">
|
||||
<div class="row" id="config"></div>
|
||||
<div class="row" id="editor"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script></body>
|
||||
<body class="application">
|
||||
<div class="container-fluid" id = "app"> </div>
|
||||
</body>
|
||||
</html>
|
@ -1,20 +0,0 @@
|
||||
package hep.dataforge.vis.spatial.gdml.demo
|
||||
|
||||
import hep.dataforge.vis.spatial.Visual3D
|
||||
import hep.dataforge.vis.spatial.VisualGroup3D
|
||||
import hep.dataforge.vis.spatial.gdml.LUnit
|
||||
import hep.dataforge.vis.spatial.gdml.readFile
|
||||
import hep.dataforge.vis.spatial.gdml.toVisual
|
||||
import hep.dataforge.vis.spatial.stringify
|
||||
import scientifik.gdml.GDML
|
||||
import java.io.File
|
||||
import java.nio.file.Paths
|
||||
|
||||
fun main() {
|
||||
val gdml = GDML.readFile(Paths.get("D:\\Work\\Projects\\gdml.kt\\gdml-source\\cubes.gdml"))
|
||||
val visual = gdml.toVisual {
|
||||
lUnit = LUnit.CM
|
||||
}
|
||||
val json = visual.stringify()
|
||||
File("D:\\Work\\Projects\\gdml.kt\\gdml-source\\cubes.json").writeText(json)
|
||||
}
|
@ -1,13 +1,13 @@
|
||||
package hep.dataforge.vis.spatial.gdml.demo
|
||||
package hep.dataforge.vision.gdml.demo
|
||||
|
||||
import hep.dataforge.context.Global
|
||||
import hep.dataforge.vis.editor.VisualObjectEditorFragment
|
||||
import hep.dataforge.vis.editor.VisualObjectTreeFragment
|
||||
import hep.dataforge.vis.spatial.Material3D
|
||||
import hep.dataforge.vis.spatial.Visual3D
|
||||
import hep.dataforge.vis.spatial.VisualGroup3D
|
||||
import hep.dataforge.vis.spatial.fx.FX3DPlugin
|
||||
import hep.dataforge.vis.spatial.fx.FXCanvas3D
|
||||
import hep.dataforge.vision.editor.VisualObjectEditorFragment
|
||||
import hep.dataforge.vision.editor.VisualObjectTreeFragment
|
||||
import hep.dataforge.vision.gdml.toVision
|
||||
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
|
||||
import javafx.geometry.Orientation
|
||||
import javafx.scene.Parent
|
||||
import javafx.stage.FileChooser
|
||||
@ -24,9 +24,9 @@ class GDMLView : View() {
|
||||
}
|
||||
|
||||
private val propertyEditor = VisualObjectEditorFragment {
|
||||
it.allProperties()
|
||||
it.getAllProperties()
|
||||
}.apply {
|
||||
descriptorProperty.set(Material3D.descriptor)
|
||||
descriptorProperty.set(SolidMaterial.descriptor)
|
||||
itemProperty.bind(treeFragment.selectedProperty)
|
||||
}
|
||||
|
||||
@ -36,10 +36,15 @@ class GDMLView : View() {
|
||||
buttonbar {
|
||||
button("Load GDML/json") {
|
||||
action {
|
||||
val file = chooseFile("Select a GDML/json file", filters = fileNameFilter).firstOrNull()
|
||||
?: return@action
|
||||
val visual: VisualGroup3D = Visual3D.readFile(file)
|
||||
canvas.render(visual)
|
||||
runAsync {
|
||||
val file = chooseFile("Select a GDML/json file", filters = fileNameFilter).firstOrNull()
|
||||
?: return@runAsync null
|
||||
SolidManager.readFile(file)
|
||||
} ui {
|
||||
if (it != null) {
|
||||
canvas.render(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -51,6 +56,14 @@ class GDMLView : View() {
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
runAsync {
|
||||
cubes().toVision()
|
||||
} ui {
|
||||
canvas.render(it)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val fileNameFilter = arrayOf(
|
||||
FileChooser.ExtensionFilter("GDML", "*.gdml", "*.xml"),
|
@ -1,51 +1,54 @@
|
||||
package hep.dataforge.vis.spatial.gdml.demo
|
||||
package hep.dataforge.vision.gdml.demo
|
||||
|
||||
import hep.dataforge.meta.setProperty
|
||||
import hep.dataforge.meta.DFExperimental
|
||||
import hep.dataforge.meta.setItem
|
||||
import hep.dataforge.values.asValue
|
||||
import hep.dataforge.vis.spatial.Material3D
|
||||
import hep.dataforge.vis.spatial.Visual3D
|
||||
import hep.dataforge.vis.spatial.VisualGroup3D
|
||||
import hep.dataforge.vis.spatial.gdml.LUnit
|
||||
import hep.dataforge.vis.spatial.gdml.readFile
|
||||
import hep.dataforge.vis.spatial.gdml.toVisual
|
||||
import hep.dataforge.vision.gdml.LUnit
|
||||
import hep.dataforge.vision.gdml.readFile
|
||||
import hep.dataforge.vision.gdml.toVision
|
||||
import hep.dataforge.vision.solid.SolidGroup
|
||||
import hep.dataforge.vision.solid.SolidManager
|
||||
import hep.dataforge.vision.solid.SolidMaterial
|
||||
import scientifik.gdml.GDML
|
||||
import java.io.File
|
||||
import java.util.zip.GZIPInputStream
|
||||
import java.util.zip.ZipInputStream
|
||||
|
||||
fun Visual3D.Companion.readFile(file: File): VisualGroup3D = when {
|
||||
@OptIn(DFExperimental::class)
|
||||
fun SolidManager.Companion.readFile(file: File): SolidGroup = when {
|
||||
file.extension == "gdml" || file.extension == "xml" -> {
|
||||
GDML.readFile(file.toPath()).toVisual {
|
||||
GDML.readFile(file.toPath()).toVision {
|
||||
lUnit = LUnit.CM
|
||||
|
||||
solidConfiguration = { parent, solid ->
|
||||
if (solid.name == "cave") {
|
||||
setProperty(Material3D.MATERIAL_WIREFRAME_KEY, true.asValue())
|
||||
setItem(SolidMaterial.MATERIAL_WIREFRAME_KEY, true.asValue())
|
||||
}
|
||||
if (parent.physVolumes.isNotEmpty()) {
|
||||
useStyle("opaque") {
|
||||
Material3D.MATERIAL_OPACITY_KEY put 0.3
|
||||
SolidMaterial.MATERIAL_OPACITY_KEY put 0.3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
file.extension == "json" -> VisualGroup3D.parseJson(file.readText())
|
||||
file.extension == "json" -> SolidGroup.parseJson(file.readText())
|
||||
file.name.endsWith("json.zip") -> {
|
||||
file.inputStream().use {
|
||||
val unzip = ZipInputStream(it, Charsets.UTF_8)
|
||||
val text = unzip.readBytes().decodeToString()
|
||||
VisualGroup3D.parseJson(text)
|
||||
SolidGroup.parseJson(text)
|
||||
}
|
||||
}
|
||||
file.name.endsWith("json.gz") -> {
|
||||
file.inputStream().use {
|
||||
val unzip = GZIPInputStream(it)
|
||||
val text = unzip.readBytes().decodeToString()
|
||||
VisualGroup3D.parseJson(text)
|
||||
SolidGroup.parseJson(text)
|
||||
}
|
||||
}
|
||||
else -> error("Unknown extension ${file.extension}")
|
||||
}
|
||||
|
||||
fun Visual3D.Companion.readFile(fileName: String): VisualGroup3D = readFile(File(fileName))
|
||||
@OptIn(DFExperimental::class)
|
||||
fun SolidManager.Companion.readFile(fileName: String): SolidGroup = readFile(File(fileName))
|
@ -0,0 +1,28 @@
|
||||
package hep.dataforge.vision.gdml.demo
|
||||
|
||||
import hep.dataforge.vision.gdml.LUnit
|
||||
import hep.dataforge.vision.gdml.readFile
|
||||
import hep.dataforge.vision.gdml.toVision
|
||||
import hep.dataforge.vision.solid.stringify
|
||||
import scientifik.gdml.GDML
|
||||
import java.io.File
|
||||
import java.nio.file.Paths
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
require(args.isNotEmpty()){"At least one argument is required"}
|
||||
val inputFileName = args[0]
|
||||
require(inputFileName.endsWith(".gdml")){"GDML required"}
|
||||
val outputFileName = args.getOrNull(1)?:inputFileName.replace(".gdml",".json")
|
||||
|
||||
val gdml = GDML.readFile(Paths.get(inputFileName))
|
||||
//GDML.readFile(Paths.get("D:\\Work\\Projects\\visionforge\\visionforge-spatial-gdml\\src\\jvmTest\\resources\\gdml\\simple1.gdml"))
|
||||
|
||||
val visual = gdml.toVision {
|
||||
lUnit = LUnit.CM
|
||||
}
|
||||
|
||||
val json = visual.stringify()
|
||||
println(json)
|
||||
File(outputFileName).writeText(json)
|
||||
//File("D:\\Work\\Projects\\gdml.kt\\gdml-source\\cubes.json").writeText(json)
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package hep.dataforge.vis.spatial
|
||||
package hep.dataforge.vision.solid
|
||||
|
||||
import hep.dataforge.names.asName
|
||||
import org.junit.jupiter.api.Test
|
||||
@ -9,7 +9,7 @@ class FileSerializationTest {
|
||||
@Ignore
|
||||
fun testFileRead(){
|
||||
val text = this::class.java.getResourceAsStream("/cubes.json").readBytes().decodeToString()
|
||||
val visual = VisualGroup3D.parseJson(text)
|
||||
val visual = SolidGroup.parseJson(text)
|
||||
visual["composite_001".asName()]
|
||||
}
|
||||
}
|
34
demo/muon-monitor/README.md
Normal file
@ -0,0 +1,34 @@
|
||||
|
||||
### Muon Monitor Visualization
|
||||
|
||||
This directory contains a full-stack application example built with `visionforge`.
|
||||
It is visualizing the
|
||||
[Muon Monitor](http://npm.mipt.ru/projects/physics.html#mounMonitor) experiment set-up,
|
||||
including experiment's geometry and events (particle tracks).
|
||||
|
||||
#### Reusing code and going Full-Stack with Kotlin Multiplatform
|
||||
|
||||
The application includes both server back-end generating events, as well as client
|
||||
visualization front-end.
|
||||
|
||||
As is common for Kotlin multiplatform projects, the code base of this simple application
|
||||
is put in the following main directories:
|
||||
* `commonMain` - common code, used by both JS client and JVM server. For example, the `Monitor`
|
||||
object describes general geometry definitions needed in all parts of the application.
|
||||
* `jsMain` - JavaScript client code. It performs visualization and reads events from the server.
|
||||
* `jvmMain` - JVM server code. It runs `ktor` HTTP server, responding with event data when
|
||||
client requests them.
|
||||
|
||||
Note that in a more traditional approach when client and server are developed separately
|
||||
and possibly using different languages, there would be no common code and benefits associated
|
||||
with it.
|
||||
|
||||
##### Building project
|
||||
|
||||
To run full-stack Muon Monitor Visualization application (both JVM server and Web browser front-end),
|
||||
run `demo/muon-monitor/application/run` task.
|
||||
|
||||
##### Example view:
|
||||
|
||||
![](../../doc/resources/muon-monitor.png)
|
||||
|
@ -1,3 +1,4 @@
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.MAIN_COMPILATION_NAME
|
||||
import scientifik.jsDistDirectory
|
||||
|
||||
plugins {
|
||||
@ -13,32 +14,36 @@ kotlin {
|
||||
|
||||
val installJS = tasks.getByName("jsBrowserDistribution")
|
||||
|
||||
js{
|
||||
js {
|
||||
browser {
|
||||
dceTask {
|
||||
dceOptions {
|
||||
keep("ktor-ktor-io.\$\$importsForInline\$\$.ktor-ktor-io.io.ktor.utils.io")
|
||||
}
|
||||
}
|
||||
webpackTask {
|
||||
mode = org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig.Mode.PRODUCTION
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
jvm {
|
||||
withJava()
|
||||
compilations.findByName("main").apply {
|
||||
tasks.getByName<ProcessResources>("jvmProcessResources") {
|
||||
compilations[MAIN_COMPILATION_NAME]?.apply {
|
||||
tasks.getByName<ProcessResources>(processResourcesTaskName) {
|
||||
dependsOn(installJS)
|
||||
afterEvaluate {
|
||||
from(project.jsDistDirectory)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
implementation(project(":dataforge-vis-spatial"))
|
||||
implementation(project(":visionforge-solid"))
|
||||
}
|
||||
}
|
||||
jvmMain {
|
||||
@ -50,6 +55,7 @@ kotlin {
|
||||
}
|
||||
jsMain {
|
||||
dependencies {
|
||||
implementation(project(":ui:bootstrap"))
|
||||
implementation("io.ktor:ktor-client-js:$ktorVersion")
|
||||
implementation("io.ktor:ktor-client-serialization-js:$ktorVersion")
|
||||
implementation(npm("text-encoding"))
|
||||
@ -69,6 +75,13 @@ application {
|
||||
mainClassName = "ru.mipt.npm.muon.monitor.server.MMServerKt"
|
||||
}
|
||||
|
||||
//configure<JavaFXOptions> {
|
||||
// modules("javafx.controls")
|
||||
//}
|
||||
distributions {
|
||||
main {
|
||||
contents {
|
||||
from("$buildDir/libs") {
|
||||
rename("${rootProject.name}-jvm", rootProject.name)
|
||||
into("lib")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
@file:UseSerializers(Point3DSerializer::class)
|
||||
package ru.mipt.npm.muon.monitor
|
||||
|
||||
import hep.dataforge.vis.spatial.Point3D
|
||||
import hep.dataforge.vis.spatial.Point3DSerializer
|
||||
import hep.dataforge.vision.solid.Point3D
|
||||
import hep.dataforge.vision.solid.Point3DSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
|
||||
|
@ -1,29 +1,29 @@
|
||||
package ru.mipt.npm.muon.monitor
|
||||
|
||||
import hep.dataforge.vis.removeAll
|
||||
import hep.dataforge.vis.spatial.*
|
||||
import hep.dataforge.vision.removeAll
|
||||
import hep.dataforge.vision.solid.*
|
||||
import ru.mipt.npm.muon.monitor.Monitor.CENTRAL_LAYER_Z
|
||||
import ru.mipt.npm.muon.monitor.Monitor.LOWER_LAYER_Z
|
||||
import ru.mipt.npm.muon.monitor.Monitor.UPPER_LAYER_Z
|
||||
import kotlin.math.PI
|
||||
|
||||
class Model {
|
||||
private val map = HashMap<String, VisualGroup3D>()
|
||||
private val map = HashMap<String, SolidGroup>()
|
||||
private val events = HashSet<Event>()
|
||||
|
||||
private fun VisualGroup3D.pixel(pixel: SC1) {
|
||||
private fun SolidGroup.pixel(pixel: SC1) {
|
||||
val group = group(pixel.name) {
|
||||
position = Point3D(pixel.center.x, pixel.center.y, pixel.center.z)
|
||||
box(pixel.xSize, pixel.ySize, pixel.zSize)
|
||||
label(pixel.name) {
|
||||
z = - Monitor.PIXEL_Z_SIZE / 2 - 5
|
||||
z = -Monitor.PIXEL_Z_SIZE / 2 - 5
|
||||
rotationY = PI
|
||||
}
|
||||
}
|
||||
map[pixel.name] = group
|
||||
}
|
||||
|
||||
private fun VisualGroup3D.detector(detector: SC16) {
|
||||
private fun SolidGroup.detector(detector: SC16) {
|
||||
group(detector.name) {
|
||||
detector.pixels.forEach {
|
||||
pixel(it)
|
||||
@ -31,9 +31,9 @@ class Model {
|
||||
}
|
||||
}
|
||||
|
||||
var tracks: VisualGroup3D
|
||||
var tracks: SolidGroup
|
||||
|
||||
val root: VisualGroup3D = VisualGroup3D().apply {
|
||||
val root: SolidGroup = SolidGroup().apply {
|
||||
rotationX = PI / 2
|
||||
group("bottom") {
|
||||
Monitor.detectors.filter { it.center.z == LOWER_LAYER_Z }.forEach {
|
||||
@ -62,7 +62,8 @@ class Model {
|
||||
|
||||
fun reset() {
|
||||
map.values.forEach {
|
||||
it.setProperty(Material3D.MATERIAL_COLOR_KEY, null)
|
||||
it.config
|
||||
it.setItem(SolidMaterial.MATERIAL_COLOR_KEY, null)
|
||||
}
|
||||
tracks.removeAll()
|
||||
}
|
||||
@ -73,7 +74,7 @@ class Model {
|
||||
highlight(it)
|
||||
}
|
||||
event.track?.let {
|
||||
tracks.polyline(*it.toTypedArray(), name = "track[${event.id}]"){
|
||||
tracks.polyline(*it.toTypedArray(), name = "track[${event.id}]") {
|
||||
thickness = 4
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
package ru.mipt.npm.muon.monitor
|
||||
|
||||
import hep.dataforge.vis.spatial.Point3D
|
||||
import hep.dataforge.vis.spatial.plus
|
||||
import hep.dataforge.vision.solid.Point3D
|
||||
import hep.dataforge.vision.solid.plus
|
||||
import ru.mipt.npm.muon.monitor.Monitor.PIXEL_XY_SIZE
|
||||
import ru.mipt.npm.muon.monitor.Monitor.PIXEL_Z_SIZE
|
||||
|
||||
/**
|
||||
* A single pixel
|
||||
*/
|
||||
open class SC1(
|
||||
class SC1(
|
||||
val name: String,
|
||||
val center: Point3D,
|
||||
val xSize: Double = PIXEL_XY_SIZE, val ySize: Double = PIXEL_XY_SIZE, val zSize: Double = PIXEL_Z_SIZE
|
||||
|
@ -0,0 +1,166 @@
|
||||
package ru.mipt.npm.muon.monitor
|
||||
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.NameToken
|
||||
import hep.dataforge.names.isEmpty
|
||||
import hep.dataforge.vision.Vision
|
||||
import hep.dataforge.vision.bootstrap.card
|
||||
import hep.dataforge.vision.bootstrap.objectTree
|
||||
import hep.dataforge.vision.react.component
|
||||
import hep.dataforge.vision.react.configEditor
|
||||
import hep.dataforge.vision.react.state
|
||||
import hep.dataforge.vision.solid.specifications.Camera
|
||||
import hep.dataforge.vision.solid.specifications.Canvas3DOptions
|
||||
import hep.dataforge.vision.solid.three.ThreeCanvas
|
||||
import hep.dataforge.vision.solid.three.ThreeCanvasComponent
|
||||
import hep.dataforge.vision.solid.three.canvasControls
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.request.get
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.html.js.onClickFunction
|
||||
import react.RProps
|
||||
import react.dom.*
|
||||
import kotlin.math.PI
|
||||
|
||||
interface MMAppProps : RProps {
|
||||
var model: Model
|
||||
var context: Context
|
||||
var connection: HttpClient
|
||||
var selected: Name?
|
||||
}
|
||||
|
||||
private val canvasConfig = Canvas3DOptions {
|
||||
camera = Camera {
|
||||
distance = 2100.0
|
||||
latitude = PI / 6
|
||||
azimuth = PI + PI / 6
|
||||
}
|
||||
}
|
||||
|
||||
val MMApp = component<MMAppProps> { props ->
|
||||
var selected by state { props.selected }
|
||||
var canvas: ThreeCanvas? by state { null }
|
||||
|
||||
val select: (Name?) -> Unit = {
|
||||
selected = it
|
||||
}
|
||||
|
||||
val visual = props.model.root
|
||||
|
||||
div("row") {
|
||||
h1("mx-auto") {
|
||||
+"Muon monitor demo"
|
||||
}
|
||||
}
|
||||
div("row") {
|
||||
div("col-lg-3 px-0 overflow-auto") {
|
||||
//tree
|
||||
card("Object tree") {
|
||||
objectTree(visual, selected, select)
|
||||
}
|
||||
}
|
||||
div("col-lg-6") {
|
||||
//canvas
|
||||
child(ThreeCanvasComponent::class) {
|
||||
attrs {
|
||||
this.context = props.context
|
||||
this.obj = visual
|
||||
this.options = canvasConfig
|
||||
this.selected = selected
|
||||
this.clickCallback = select
|
||||
this.canvasCallback = {
|
||||
canvas = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
div("col-lg-3") {
|
||||
div("row") {
|
||||
//settings
|
||||
canvas?.let {
|
||||
card("Canvas configuration") {
|
||||
canvasControls(it)
|
||||
}
|
||||
}
|
||||
card("Events") {
|
||||
button {
|
||||
+"Next"
|
||||
attrs {
|
||||
onClickFunction = {
|
||||
GlobalScope.launch {
|
||||
val event = props.connection.get<Event>("http://localhost:8080/event")
|
||||
props.model.displayEvent(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
button {
|
||||
+"Clear"
|
||||
attrs {
|
||||
onClickFunction = {
|
||||
props.model.reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
div("row") {
|
||||
div("container-fluid p-0") {
|
||||
nav {
|
||||
attrs {
|
||||
attributes["aria-label"] = "breadcrumb"
|
||||
}
|
||||
ol("breadcrumb") {
|
||||
li("breadcrumb-item") {
|
||||
button(classes = "btn btn-link p-0") {
|
||||
+"World"
|
||||
attrs {
|
||||
onClickFunction = {
|
||||
selected = hep.dataforge.names.Name.EMPTY
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (selected != null) {
|
||||
val tokens = ArrayList<NameToken>(selected?.length ?: 1)
|
||||
selected?.tokens?.forEach { token ->
|
||||
tokens.add(token)
|
||||
val fullName = Name(tokens.toList())
|
||||
li("breadcrumb-item") {
|
||||
button(classes = "btn btn-link p-0") {
|
||||
+token.toString()
|
||||
attrs {
|
||||
onClickFunction = {
|
||||
console.log("Selected = $fullName")
|
||||
selected = fullName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
div("row") {
|
||||
//properties
|
||||
card("Properties") {
|
||||
selected.let { selected ->
|
||||
val selectedObject: Vision? = when {
|
||||
selected == null -> null
|
||||
selected.isEmpty() -> visual
|
||||
else -> visual[selected]
|
||||
}
|
||||
if (selectedObject != null) {
|
||||
configEditor(selectedObject, default = selectedObject.getAllProperties(), key = selected)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -3,31 +3,15 @@ package ru.mipt.npm.muon.monitor
|
||||
import hep.dataforge.context.Global
|
||||
import hep.dataforge.js.Application
|
||||
import hep.dataforge.js.startApplication
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.isEmpty
|
||||
import hep.dataforge.vis.VisualGroup
|
||||
import hep.dataforge.vis.VisualObject
|
||||
import hep.dataforge.vis.editor.card
|
||||
import hep.dataforge.vis.editor.objectTree
|
||||
import hep.dataforge.vis.editor.visualPropertyEditor
|
||||
import hep.dataforge.vis.spatial.Visual3D
|
||||
import hep.dataforge.vis.spatial.VisualObject3D
|
||||
import hep.dataforge.vis.spatial.three.ThreePlugin
|
||||
import hep.dataforge.vis.spatial.three.displayCanvasControls
|
||||
import hep.dataforge.vis.spatial.three.output
|
||||
import info.laht.threekt.math.Vector3
|
||||
import hep.dataforge.vision.solid.SolidManager
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.features.json.JsonFeature
|
||||
import io.ktor.client.features.json.serializer.KotlinxSerializer
|
||||
import io.ktor.client.request.get
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.html.js.button
|
||||
import kotlinx.html.js.onClickFunction
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.w3c.dom.HTMLElement
|
||||
import react.child
|
||||
import react.dom.div
|
||||
import react.dom.render
|
||||
import kotlin.browser.document
|
||||
import kotlin.dom.clear
|
||||
|
||||
private class MMDemoApp : Application {
|
||||
|
||||
@ -35,71 +19,28 @@ private class MMDemoApp : Application {
|
||||
|
||||
private val connection = HttpClient {
|
||||
install(JsonFeature) {
|
||||
serializer = KotlinxSerializer(Json(context = Visual3D.serialModule))
|
||||
serializer = KotlinxSerializer(Json(context = SolidManager.serialModule))
|
||||
}
|
||||
}
|
||||
|
||||
//TODO introduce react application
|
||||
|
||||
override fun start(state: Map<String, Any>) {
|
||||
|
||||
val context = Global.context("demo") {}
|
||||
val three = context.plugins.load(ThreePlugin)
|
||||
//val url = URL("https://drive.google.com/open?id=1w5e7fILMN83JGgB8WANJUYm8OW2s0WVO")
|
||||
val element = document.getElementById("app") ?: error("Element with id 'app' not found on page")
|
||||
|
||||
val canvasElement = document.getElementById("canvas") ?: error("Element with id 'canvas' not found on page")
|
||||
val settingsElement = document.getElementById("settings")
|
||||
?: error("Element with id 'settings' not found on page")
|
||||
val treeElement = document.getElementById("tree") ?: error("Element with id 'tree' not found on page")
|
||||
val editorElement = document.getElementById("editor") ?: error("Element with id 'editor' not found on page")
|
||||
|
||||
canvasElement.clear()
|
||||
val visual: VisualObject3D = model.root
|
||||
|
||||
//output.camera.layers.enable(1)
|
||||
val canvas = three.output(canvasElement as HTMLElement)
|
||||
|
||||
canvas.camera.layers.set(0)
|
||||
canvas.camera.position.z = -2000.0
|
||||
canvas.camera.position.y = 500.0
|
||||
canvas.camera.lookAt(Vector3(0, 0, 0))
|
||||
settingsElement.displayCanvasControls(canvas) {
|
||||
card("Events") {
|
||||
button {
|
||||
+"Next"
|
||||
onClickFunction = {
|
||||
GlobalScope.launch {
|
||||
val event = connection.get<Event>("http://localhost:8080/event")
|
||||
model.displayEvent(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
button {
|
||||
+"Clear"
|
||||
onClickFunction = {
|
||||
model.reset()
|
||||
render(element) {
|
||||
div("container-fluid h-100") {
|
||||
child(MMApp) {
|
||||
attrs {
|
||||
model = this@MMDemoApp.model
|
||||
connection = this@MMDemoApp.connection
|
||||
this.context = context
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun selectElement(name: Name) {
|
||||
val child: VisualObject = when {
|
||||
name.isEmpty() -> visual
|
||||
visual is VisualGroup -> visual[name] ?: return
|
||||
else -> return
|
||||
}
|
||||
editorElement.visualPropertyEditor(name, child, descriptor = VisualObject3D.descriptor)
|
||||
|
||||
}
|
||||
|
||||
// canvas.clickListener = ::selectElement
|
||||
|
||||
//tree.visualObjectTree(visual, editor::propertyEditor)
|
||||
treeElement.objectTree(visual) { name ->
|
||||
selectElement(name)
|
||||
canvas.highlight(name)
|
||||
}
|
||||
canvas.render(visual)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,748 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="240"
|
||||
height="144"
|
||||
id="svg4136"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="jsoneditor-icons.svg">
|
||||
<title
|
||||
id="title6512">JSON Editor Icons</title>
|
||||
<metadata
|
||||
id="metadata4148">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title>JSON Editor Icons</dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs4146" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ff63ff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1026"
|
||||
id="namedview4144"
|
||||
showgrid="true"
|
||||
inkscape:zoom="4"
|
||||
inkscape:cx="13.229181"
|
||||
inkscape:cy="119.82429"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4136"
|
||||
showguides="false"
|
||||
borderlayer="false"
|
||||
inkscape:showpageshadow="true"
|
||||
showborder="true">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid4640"
|
||||
empspacing="24" />
|
||||
</sodipodi:namedview>
|
||||
<!-- Created with SVG-edit - http://svg-edit.googlecode.com/ -->
|
||||
<rect
|
||||
style="fill:#4c4c4c;fill-opacity:1;stroke:none;stroke-width:0"
|
||||
id="svg_1"
|
||||
height="16"
|
||||
width="16"
|
||||
y="4"
|
||||
x="4" />
|
||||
<rect
|
||||
id="svg_1-7"
|
||||
height="16"
|
||||
width="16"
|
||||
y="3.999995"
|
||||
x="28.000006"
|
||||
style="fill:#ec3f29;fill-opacity:0.94117647;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
style="fill:#4c4c4c;fill-opacity:1;stroke:none;stroke-width:0"
|
||||
x="52.000004"
|
||||
y="3.999995"
|
||||
width="16"
|
||||
height="16"
|
||||
id="rect4165" />
|
||||
<rect
|
||||
id="rect4175"
|
||||
height="16"
|
||||
width="16"
|
||||
y="3.9999852"
|
||||
x="172.00002"
|
||||
style="fill:#4c4c4c;fill-opacity:1;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
id="rect4175-3"
|
||||
height="16"
|
||||
width="16"
|
||||
y="3.999995"
|
||||
x="196"
|
||||
style="fill:#4c4c4c;fill-opacity:1;stroke:none;stroke-width:0" />
|
||||
<g
|
||||
id="g4299"
|
||||
style="stroke:none">
|
||||
<rect
|
||||
x="7.0000048"
|
||||
y="10.999998"
|
||||
width="9.9999924"
|
||||
height="1.9999986"
|
||||
id="svg_1-1"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
x="11.000005"
|
||||
y="7.0000114"
|
||||
width="1.9999955"
|
||||
height="9.9999838"
|
||||
id="svg_1-1-1"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0" />
|
||||
</g>
|
||||
<g
|
||||
id="g4299-3"
|
||||
transform="matrix(0.70710678,-0.70710678,0.70710678,0.70710678,19.029435,12.000001)"
|
||||
style="stroke:none">
|
||||
<rect
|
||||
x="7.0000048"
|
||||
y="10.999998"
|
||||
width="9.9999924"
|
||||
height="1.9999986"
|
||||
id="svg_1-1-0"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
x="11.000005"
|
||||
y="7.0000114"
|
||||
width="1.9999955"
|
||||
height="9.9999838"
|
||||
id="svg_1-1-1-9"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0" />
|
||||
</g>
|
||||
<rect
|
||||
id="svg_1-7-5"
|
||||
height="6.9999905"
|
||||
width="6.9999909"
|
||||
y="7.0000048"
|
||||
x="55.000004"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#4c4c4c;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
x="58"
|
||||
y="10.00001"
|
||||
width="6.9999909"
|
||||
height="6.9999905"
|
||||
id="rect4354" />
|
||||
<rect
|
||||
id="svg_1-7-5-7"
|
||||
height="6.9999905"
|
||||
width="6.9999909"
|
||||
y="10.000005"
|
||||
x="58.000004"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#3c80df;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.94117647" />
|
||||
<g
|
||||
id="g4378">
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0"
|
||||
x="198"
|
||||
y="10.999999"
|
||||
width="7.9999909"
|
||||
height="1.9999965"
|
||||
id="svg_1-7-5-3" />
|
||||
<rect
|
||||
id="rect4374"
|
||||
height="1.9999946"
|
||||
width="11.999995"
|
||||
y="7.0000005"
|
||||
x="198"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0" />
|
||||
<rect
|
||||
id="rect4376"
|
||||
height="1.9999995"
|
||||
width="3.9999928"
|
||||
y="14.999996"
|
||||
x="198"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0" />
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(1,0,0,-1,-23.999995,23.999995)"
|
||||
id="g4383">
|
||||
<rect
|
||||
id="rect4385"
|
||||
height="1.9999965"
|
||||
width="7.9999909"
|
||||
y="10.999999"
|
||||
x="198"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0"
|
||||
x="198"
|
||||
y="7.0000005"
|
||||
width="11.999995"
|
||||
height="1.9999946"
|
||||
id="rect4387" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0"
|
||||
x="198"
|
||||
y="14.999996"
|
||||
width="3.9999928"
|
||||
height="1.9999995"
|
||||
id="rect4389" />
|
||||
</g>
|
||||
<rect
|
||||
style="fill:#4c4c4c;fill-opacity:1;stroke:none"
|
||||
id="rect3754-4"
|
||||
width="16"
|
||||
height="16"
|
||||
x="76"
|
||||
y="3.9999199" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 85.10447,6.0157384 -0.0156,1.4063 c 3.02669,-0.2402 0.33008,3.6507996 2.48438,4.5780996 -2.18694,1.0938 0.49191,4.9069 -2.45313,4.5781 l -0.0156,1.4219 c 5.70828,0.559 1.03264,-5.1005 4.70313,-5.2656 l 0,-1.4063 c -3.61303,-0.027 1.11893,-5.7069996 -4.70313,-5.3124996 z"
|
||||
id="path4351"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccccccc" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 82.78125,5.9984384 0.0156,1.4063 c -3.02668,-0.2402 -0.33007,3.6506996 -2.48437,4.5780996 2.18694,1.0938 -0.49192,4.9069 2.45312,4.5781 l 0.0156,1.4219 c -5.70827,0.559 -1.03263,-5.1004 -4.70312,-5.2656 l 0,-1.4063 c 3.61303,-0.027 -1.11894,-5.7070996 4.70312,-5.3124996 z"
|
||||
id="path4351-9"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccccccc" />
|
||||
<rect
|
||||
style="fill:#4c4c4c;fill-opacity:1;stroke:none"
|
||||
id="rect3754-25"
|
||||
width="16"
|
||||
height="16"
|
||||
x="100"
|
||||
y="3.9999199" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none"
|
||||
d="m 103.719,5.6719384 0,12.7187996 3.03125,0 0,-1.5313 -1.34375,0 0,-9.6249996 1.375,0 0,-1.5625 z"
|
||||
id="path2987"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none"
|
||||
d="m 112.2185,5.6721984 0,12.7187996 -3.03125,0 0,-1.5313 1.34375,0 0,-9.6249996 -1.375,0 0,-1.5625 z"
|
||||
id="path2987-1"
|
||||
inkscape:connector-curvature="0" />
|
||||
<rect
|
||||
style="fill:#4c4c4c;fill-opacity:1;stroke:none"
|
||||
id="rect3754-73"
|
||||
width="16"
|
||||
height="16"
|
||||
x="124"
|
||||
y="3.9999199" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none"
|
||||
d="m 126.2824,17.602938 1.78957,0 1.14143,-2.8641 5.65364,0 1.14856,2.8641 1.76565,0 -4.78687,-11.1610996 -1.91903,0 z"
|
||||
id="path3780"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccccccc" />
|
||||
<path
|
||||
style="fill:#4c4c4c;fill-opacity:1;stroke:none"
|
||||
d="m 129.72704,13.478838 4.60852,0.01 -2.30426,-5.5497996 z"
|
||||
id="path3782"
|
||||
inkscape:connector-curvature="0" />
|
||||
<rect
|
||||
style="fill:#4c4c4c;fill-opacity:1;stroke:none"
|
||||
id="rect3754-35"
|
||||
width="16"
|
||||
height="16"
|
||||
x="148"
|
||||
y="3.9999199" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none"
|
||||
d="m 156.47655,5.8917384 0,2.1797 0.46093,2.3983996 1.82813,0 0.39844,-2.3983996 0,-2.1797 z"
|
||||
id="path5008-2"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccccc" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none"
|
||||
d="m 152.51561,5.8906384 0,2.1797 0.46094,2.3983996 1.82812,0 0.39844,-2.3983996 0,-2.1797 z"
|
||||
id="path5008-2-8"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccccc" />
|
||||
<rect
|
||||
id="svg_1-7-2"
|
||||
height="1.9999961"
|
||||
width="11.999996"
|
||||
y="64"
|
||||
x="54"
|
||||
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
id="svg_1-7-2-2"
|
||||
height="2.9999905"
|
||||
width="2.9999907"
|
||||
y="52"
|
||||
x="80.000008"
|
||||
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
|
||||
x="85.000008"
|
||||
y="52"
|
||||
width="2.9999907"
|
||||
height="2.9999905"
|
||||
id="rect4561" />
|
||||
<rect
|
||||
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
|
||||
x="80.000008"
|
||||
y="58"
|
||||
width="2.9999907"
|
||||
height="2.9999905"
|
||||
id="rect4563" />
|
||||
<rect
|
||||
id="rect4565"
|
||||
height="2.9999905"
|
||||
width="2.9999907"
|
||||
y="58"
|
||||
x="85.000008"
|
||||
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
id="rect4567"
|
||||
height="2.9999905"
|
||||
width="2.9999907"
|
||||
y="64"
|
||||
x="80.000008"
|
||||
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
|
||||
x="85.000008"
|
||||
y="64"
|
||||
width="2.9999907"
|
||||
height="2.9999905"
|
||||
id="rect4569" />
|
||||
<circle
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#4c4c4c;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
id="path4571"
|
||||
cx="110.06081"
|
||||
cy="57.939209"
|
||||
r="4.7438836" />
|
||||
<rect
|
||||
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
|
||||
x="116.64566"
|
||||
y="-31.79752"
|
||||
width="4.229713"
|
||||
height="6.4053884"
|
||||
id="rect4563-2"
|
||||
transform="matrix(0.70710678,0.70710678,-0.70710678,0.70710678,0,0)" />
|
||||
<path
|
||||
style="fill:#4c4c4c;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 125,56 138.77027,56.095 132,64 Z"
|
||||
id="path4613"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<path
|
||||
sodipodi:nodetypes="cccc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path4615"
|
||||
d="M 149,64 162.77027,63.905 156,56 Z"
|
||||
style="fill:#4c4c4c;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
|
||||
x="54"
|
||||
y="53"
|
||||
width="11.999996"
|
||||
height="1.9999961"
|
||||
id="rect4638" />
|
||||
<rect
|
||||
id="svg_1-7-2-24"
|
||||
height="1.9999957"
|
||||
width="12.99999"
|
||||
y="-56"
|
||||
x="53"
|
||||
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
|
||||
transform="matrix(0,1,-1,0,0,0)" />
|
||||
<rect
|
||||
transform="matrix(0,1,-1,0,0,0)"
|
||||
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
|
||||
x="53"
|
||||
y="-66"
|
||||
width="12.99999"
|
||||
height="1.9999957"
|
||||
id="rect4657" />
|
||||
<rect
|
||||
id="rect4659"
|
||||
height="0.99999291"
|
||||
width="11.999999"
|
||||
y="57"
|
||||
x="54"
|
||||
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
|
||||
x="54"
|
||||
y="88.000122"
|
||||
width="11.999996"
|
||||
height="1.9999961"
|
||||
id="rect4661" />
|
||||
<rect
|
||||
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
|
||||
x="80.000008"
|
||||
y="76.000122"
|
||||
width="2.9999907"
|
||||
height="2.9999905"
|
||||
id="rect4663" />
|
||||
<rect
|
||||
id="rect4665"
|
||||
height="2.9999905"
|
||||
width="2.9999907"
|
||||
y="76.000122"
|
||||
x="85.000008"
|
||||
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1" />
|
||||
<rect
|
||||
id="rect4667"
|
||||
height="2.9999905"
|
||||
width="2.9999907"
|
||||
y="82.000122"
|
||||
x="80.000008"
|
||||
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1" />
|
||||
<rect
|
||||
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
|
||||
x="85.000008"
|
||||
y="82.000122"
|
||||
width="2.9999907"
|
||||
height="2.9999905"
|
||||
id="rect4669" />
|
||||
<rect
|
||||
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
|
||||
x="80.000008"
|
||||
y="88.000122"
|
||||
width="2.9999907"
|
||||
height="2.9999905"
|
||||
id="rect4671" />
|
||||
<rect
|
||||
id="rect4673"
|
||||
height="2.9999905"
|
||||
width="2.9999907"
|
||||
y="88.000122"
|
||||
x="85.000008"
|
||||
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1" />
|
||||
<circle
|
||||
r="4.7438836"
|
||||
cy="81.939331"
|
||||
cx="110.06081"
|
||||
id="circle4675"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#d3d3d3;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
transform="matrix(0.70710678,0.70710678,-0.70710678,0.70710678,0,0)"
|
||||
id="rect4677"
|
||||
height="6.4053884"
|
||||
width="4.229713"
|
||||
y="-14.826816"
|
||||
x="133.6163"
|
||||
style="fill:#d3d3d3;fill-opacity:1;stroke:#d3d3d3;stroke-width:0;stroke-opacity:1" />
|
||||
<path
|
||||
sodipodi:nodetypes="cccc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path4679"
|
||||
d="m 125,80.000005 13.77027,0.09499 L 132,87.999992 Z"
|
||||
style="fill:#d3d3d3;fill-opacity:1;fill-rule:evenodd;stroke:#d3d3d3;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:#d3d3d3;fill-opacity:1;fill-rule:evenodd;stroke:#d3d3d3;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 149,88.0002 162.77027,87.9052 156,80.0002 Z"
|
||||
id="path4681"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<rect
|
||||
id="rect4683"
|
||||
height="1.9999961"
|
||||
width="11.999996"
|
||||
y="77.000122"
|
||||
x="54"
|
||||
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1" />
|
||||
<rect
|
||||
transform="matrix(0,1,-1,0,0,0)"
|
||||
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
|
||||
x="77.000122"
|
||||
y="-56"
|
||||
width="12.99999"
|
||||
height="1.9999957"
|
||||
id="rect4685" />
|
||||
<rect
|
||||
id="rect4687"
|
||||
height="1.9999957"
|
||||
width="12.99999"
|
||||
y="-66"
|
||||
x="77.000122"
|
||||
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
|
||||
transform="matrix(0,1,-1,0,0,0)" />
|
||||
<rect
|
||||
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
|
||||
x="54"
|
||||
y="81.000122"
|
||||
width="11.999999"
|
||||
height="0.99999291"
|
||||
id="rect4689" />
|
||||
<rect
|
||||
id="rect4761-1"
|
||||
height="1.9999945"
|
||||
width="15.99999"
|
||||
y="101"
|
||||
x="76.000008"
|
||||
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
id="rect4761-0"
|
||||
height="1.9999945"
|
||||
width="15.99999"
|
||||
y="105"
|
||||
x="76.000008"
|
||||
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
id="rect4761-7"
|
||||
height="1.9999945"
|
||||
width="9"
|
||||
y="109"
|
||||
x="76.000008"
|
||||
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
id="rect4761-1-1"
|
||||
height="1.9999945"
|
||||
width="12"
|
||||
y="125"
|
||||
x="76.000008"
|
||||
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
id="rect4761-1-1-4"
|
||||
height="1.9999945"
|
||||
width="10"
|
||||
y="137"
|
||||
x="76.000008"
|
||||
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
id="rect4761-1-1-4-4"
|
||||
height="1.9999945"
|
||||
width="10"
|
||||
y="129"
|
||||
x="82"
|
||||
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
id="rect4761-1-1-4-4-3"
|
||||
height="1.9999945"
|
||||
width="9"
|
||||
y="133"
|
||||
x="82"
|
||||
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.8;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.66157866;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 36.398438,100.0254 c -0.423362,-0.013 -0.846847,0.01 -1.265626,0.062 -1.656562,0.2196 -3.244567,0.9739 -4.507812,2.2266 L 29,100.5991 l -2.324219,7.7129 7.826172,-1.9062 -1.804687,-1.9063 c 1.597702,-1.5308 4.048706,-1.8453 5.984375,-0.7207 1.971162,1.1452 2.881954,3.3975 2.308593,5.5508 -0.573361,2.1533 -2.533865,3.6953 -4.830078,3.6953 l 0,3.0742 c 3.550756,0 6.710442,-2.4113 7.650391,-5.9414 0.939949,-3.5301 -0.618463,-7.2736 -3.710938,-9.0703 -1.159678,-0.6738 -2.431087,-1.0231 -3.701171,-1.0625 z"
|
||||
id="path4138" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.8;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.66157866;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 59.722656,99.9629 c -1.270084,0.039 -2.541493,0.3887 -3.701172,1.0625 -3.092475,1.7967 -4.650886,5.5402 -3.710937,9.0703 0.939949,3.5301 4.09768,5.9414 7.648437,5.9414 l 0,-3.0742 c -2.296214,0 -4.256717,-1.542 -4.830078,-3.6953 -0.573361,-2.1533 0.337432,-4.4056 2.308594,-5.5508 1.935731,-1.1246 4.38863,-0.8102 5.986326,0.7207 l -1.806638,1.9063 7.828128,1.9062 -2.32422,-7.7129 -1.62696,1.7168 c -1.26338,-1.2531 -2.848917,-2.0088 -4.505855,-2.2285 -0.418778,-0.055 -0.842263,-0.076 -1.265625,-0.062 z"
|
||||
id="path4138-1" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="opacity:0.8;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.96599996;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
d="m 10.5,100 0,2 -2.4999996,0 L 12,107 l 4,-5 -2.5,0 0,-2 -3,0 z"
|
||||
id="path3055-0-77" />
|
||||
<path
|
||||
style="opacity:0.8;fill:none;stroke:#ffffff;stroke-width:1.96599996;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 4.9850574,108.015 14.0298856,-0.03"
|
||||
id="path5244-5-0-5"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="opacity:0.8;fill:none;stroke:#ffffff;stroke-width:1.96599996;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 4.9849874,132.015 14.0298866,-0.03"
|
||||
id="path5244-5-0-5-8"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.4;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#4d4d4d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.66157866;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 36.398438,123.9629 c -0.423362,-0.013 -0.846847,0.01 -1.265626,0.062 -1.656562,0.2196 -3.244567,0.9739 -4.507812,2.2266 L 29,124.5366 l -2.324219,7.7129 7.826172,-1.9062 -1.804687,-1.9063 c 1.597702,-1.5308 4.048706,-1.8453 5.984375,-0.7207 1.971162,1.1453 2.881954,3.3975 2.308593,5.5508 -0.573361,2.1533 -2.533864,3.6953 -4.830078,3.6953 l 0,3.0742 c 3.550757,0 6.710442,-2.4093 7.650391,-5.9394 0.939949,-3.5301 -0.618463,-7.2756 -3.710938,-9.0723 -1.159678,-0.6737 -2.431087,-1.0231 -3.701171,-1.0625 z"
|
||||
id="path4138-12" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.4;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#4d4d4d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.66157866;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 59.722656,123.9629 c -1.270084,0.039 -2.541493,0.3888 -3.701172,1.0625 -3.092475,1.7967 -4.650886,5.5422 -3.710937,9.0723 0.939949,3.5301 4.09768,5.9394 7.648437,5.9394 l 0,-3.0742 c -2.296214,0 -4.256717,-1.542 -4.830078,-3.6953 -0.573361,-2.1533 0.337432,-4.4055 2.308594,-5.5508 1.935731,-1.1246 4.38863,-0.8102 5.986326,0.7207 l -1.806638,1.9063 7.828128,1.9062 -2.32422,-7.7129 -1.62696,1.7168 c -1.26338,-1.2531 -2.848917,-2.0088 -4.505855,-2.2285 -0.418778,-0.055 -0.842263,-0.076 -1.265625,-0.062 z"
|
||||
id="path4138-1-3" />
|
||||
<path
|
||||
id="path6191"
|
||||
d="m 10.5,116 0,-2 -2.4999996,0 L 12,109 l 4,5 -2.5,0 0,2 -3,0 z"
|
||||
style="opacity:0.8;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.96599996;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="opacity:0.8;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.96599996;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
d="m 10.5,129 0,-2 -2.4999996,0 L 12,122 l 4,5 -2.5,0 0,2 -3,0 z"
|
||||
id="path6193" />
|
||||
<path
|
||||
id="path6195"
|
||||
d="m 10.5,135 0,2 -2.4999996,0 L 12,142 l 4,-5 -2.5,0 0,-2 -3,0 z"
|
||||
style="opacity:0.8;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.96599996;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
sodipodi:type="star"
|
||||
style="fill:#4d4d4d;fill-opacity:0.90196078;stroke:#d3d3d3;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
id="path4500"
|
||||
sodipodi:sides="3"
|
||||
sodipodi:cx="11.55581"
|
||||
sodipodi:cy="60.073242"
|
||||
sodipodi:r1="5.1116104"
|
||||
sodipodi:r2="2.5558052"
|
||||
sodipodi:arg1="0"
|
||||
sodipodi:arg2="1.0471976"
|
||||
inkscape:flatsided="false"
|
||||
inkscape:rounded="0"
|
||||
inkscape:randomized="0"
|
||||
d="m 16.66742,60.073242 -3.833708,2.213392 -3.8337072,2.213393 0,-4.426785 0,-4.426784 3.8337082,2.213392 z"
|
||||
inkscape:transform-center-x="-1.2779026" />
|
||||
<path
|
||||
inkscape:transform-center-x="1.277902"
|
||||
d="m -31.500004,60.073242 -3.833708,2.213392 -3.833707,2.213393 0,-4.426785 0,-4.426784 3.833707,2.213392 z"
|
||||
inkscape:randomized="0"
|
||||
inkscape:rounded="0"
|
||||
inkscape:flatsided="false"
|
||||
sodipodi:arg2="1.0471976"
|
||||
sodipodi:arg1="0"
|
||||
sodipodi:r2="2.5558052"
|
||||
sodipodi:r1="5.1116104"
|
||||
sodipodi:cy="60.073242"
|
||||
sodipodi:cx="-36.611614"
|
||||
sodipodi:sides="3"
|
||||
id="path4502"
|
||||
style="fill:#4d4d4d;fill-opacity:0.90196078;stroke:#d3d3d3;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
sodipodi:type="star"
|
||||
transform="scale(-1,1)" />
|
||||
<path
|
||||
d="m 16.66742,60.073212 -3.833708,2.213392 -3.8337072,2.213392 0,-4.426784 0,-4.426785 3.8337082,2.213392 z"
|
||||
inkscape:randomized="0"
|
||||
inkscape:rounded="0"
|
||||
inkscape:flatsided="false"
|
||||
sodipodi:arg2="1.0471976"
|
||||
sodipodi:arg1="0"
|
||||
sodipodi:r2="2.5558052"
|
||||
sodipodi:r1="5.1116104"
|
||||
sodipodi:cy="60.073212"
|
||||
sodipodi:cx="11.55581"
|
||||
sodipodi:sides="3"
|
||||
id="path4504"
|
||||
style="fill:#4d4d4d;fill-opacity:0.90196078;stroke:#d3d3d3;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
sodipodi:type="star"
|
||||
transform="matrix(0,1,-1,0,72.0074,71.7877)"
|
||||
inkscape:transform-center-y="1.2779029" />
|
||||
<path
|
||||
inkscape:transform-center-y="-1.2779026"
|
||||
transform="matrix(0,-1,-1,0,96,96)"
|
||||
sodipodi:type="star"
|
||||
style="fill:#4d4d4d;fill-opacity:0.90196078;stroke:#d3d3d3;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
id="path4506"
|
||||
sodipodi:sides="3"
|
||||
sodipodi:cx="11.55581"
|
||||
sodipodi:cy="60.073212"
|
||||
sodipodi:r1="5.1116104"
|
||||
sodipodi:r2="2.5558052"
|
||||
sodipodi:arg1="0"
|
||||
sodipodi:arg2="1.0471976"
|
||||
inkscape:flatsided="false"
|
||||
inkscape:rounded="0"
|
||||
inkscape:randomized="0"
|
||||
d="m 16.66742,60.073212 -3.833708,2.213392 -3.8337072,2.213392 0,-4.426784 0,-4.426785 3.8337082,2.213392 z" />
|
||||
<path
|
||||
sodipodi:nodetypes="cccc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path4615-5"
|
||||
d="m 171.82574,65.174193 16.34854,0 -8.17427,-13.348454 z"
|
||||
style="fill:#fbb917;fill-opacity:1;fill-rule:evenodd;stroke:#fbb917;stroke-width:1.65161395;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 179,55 0,6 2,0 0,-6"
|
||||
id="path4300"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<path
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 179,62 0,2 2,0 0,-2"
|
||||
id="path4300-6"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:0.8;fill-rule:evenodd;stroke:#ffffff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:0.8"
|
||||
d="M 99.994369,113.0221 102,114.98353 l 7,-6.9558 3,0.97227 2,-1 1,-2 0,-3 -3,3 -3,-3 3,-3 -3,0 -2,1 -1,2 0.99437,3.0221 z"
|
||||
id="path4268"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccccccccccccc" />
|
||||
<rect
|
||||
id="rect4175-3-5"
|
||||
height="16"
|
||||
width="16"
|
||||
y="4"
|
||||
x="220"
|
||||
style="fill:#4c4c4c;fill-opacity:1;stroke:none;stroke-width:0" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 234,6 0,2 -5,5 0,5 -2,0 0,-5 -5,-5 0,-2"
|
||||
id="path3546"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccccccc" />
|
||||
<g
|
||||
transform="matrix(1.3333328,0,0,-1.5999992,-139.9999,127.19999)"
|
||||
id="g4383-6">
|
||||
<rect
|
||||
id="rect4385-2"
|
||||
height="1.2499905"
|
||||
width="5.9999924"
|
||||
y="12.625005"
|
||||
x="198.00002"
|
||||
style="fill:#ffffff;fill-opacity:0.8;stroke:#000000;stroke-width:0" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:0.8;stroke:#000000;stroke-width:0"
|
||||
x="198.00002"
|
||||
y="15.125007"
|
||||
width="7.4999928"
|
||||
height="1.2499949"
|
||||
id="rect4387-9" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:0.8;stroke:#000000;stroke-width:0"
|
||||
x="198.00002"
|
||||
y="7.6250024"
|
||||
width="2.9999909"
|
||||
height="1.2499905"
|
||||
id="rect4389-1-0" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:0.8;stroke:#000000;stroke-width:0"
|
||||
x="198.00002"
|
||||
y="10.125004"
|
||||
width="4.4999919"
|
||||
height="1.2499905"
|
||||
id="rect4389-1-9" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:0.8;fill-rule:evenodd;stroke:none;stroke-width:0.68465352px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 207.00001,16.375004 0,-5.625005 -2.25,0 3,-3.1250014 3,3.1250014 -2.25,0 0,5.625005 -1.5,0"
|
||||
id="path4402"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccccccc" />
|
||||
</g>
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:0.8;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 164,100 0,3 -6,6 0,7 -4,0 0,-7 -6,-6 0,-3"
|
||||
id="path3546-2-2"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccccccc" />
|
||||
<rect
|
||||
style="fill:#4c4c4c;fill-opacity:1;stroke:none;stroke-width:0"
|
||||
id="svg_1-3"
|
||||
height="16"
|
||||
width="16"
|
||||
y="28"
|
||||
x="4" />
|
||||
<path
|
||||
sodipodi:nodetypes="ccccccccc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path4402-5-7"
|
||||
d="m 15,41 0,-7 -4,0 0,3 -5,-4 5,-4 0,3 6,0 0,9"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.68465352px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
</svg>
|
Before Width: | Height: | Size: 31 KiB |
@ -4,29 +4,13 @@
|
||||
<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.3.1/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="css/main.css">
|
||||
<script type="text/javascript" src="main.bundle.js"></script>
|
||||
<script type="text/javascript" src ="js/jquery-3.4.1.min.js"></script>
|
||||
<script type="text/javascript" src ="js/bootstrap.bundle.min.js"></script>
|
||||
</head>
|
||||
<body class="testApp">
|
||||
|
||||
<div class="container">
|
||||
<h1>Muon monitor demo</h1>
|
||||
</div>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-lg-3">
|
||||
<div class="row" id="tree"></div>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div id="canvas"></div>
|
||||
</div>
|
||||
<div class="col-lg-3">
|
||||
<div class="row" id="settings"></div>
|
||||
<div class="row" id="editor"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<body class="application">
|
||||
<div class="container-fluid" id = "app"> </div>
|
||||
</body>
|
||||
</html>
|
@ -1,7 +1,7 @@
|
||||
package ru.mipt.npm.muon.monitor.server
|
||||
|
||||
|
||||
import hep.dataforge.vis.spatial.Visual3D
|
||||
import hep.dataforge.vision.solid.SolidManager
|
||||
import io.ktor.application.Application
|
||||
import io.ktor.application.call
|
||||
import io.ktor.application.install
|
||||
@ -36,7 +36,7 @@ fun Application.module() {
|
||||
install(DefaultHeaders)
|
||||
install(CallLogging)
|
||||
install(ContentNegotiation) {
|
||||
json(module = Visual3D.serialModule)
|
||||
json(module = SolidManager.serialModule)
|
||||
}
|
||||
install(Routing) {
|
||||
get("/event") {
|
||||
|
@ -1,6 +1,6 @@
|
||||
package ru.mipt.npm.muon.monitor.sim
|
||||
|
||||
import hep.dataforge.vis.spatial.Point3D
|
||||
import hep.dataforge.vision.solid.Point3D
|
||||
import org.apache.commons.math3.geometry.euclidean.threed.Line
|
||||
import org.apache.commons.math3.geometry.euclidean.threed.Plane
|
||||
import org.apache.commons.math3.geometry.euclidean.threed.Vector3D
|
||||
|
19
demo/spatial-showcase/README.md
Normal file
@ -0,0 +1,19 @@
|
||||
### Spatial Showcase
|
||||
|
||||
Contains a simple demonstration with a grid including a few shapes that you can rotate, move camera, and so on.
|
||||
Some shapes will also periodically change their color and visibility.
|
||||
|
||||
##### Building project
|
||||
|
||||
To see the JS demo: run `demo/spatial-showcase/Tasks/distribution/jsBrowserDistribution` Gradle task, then open
|
||||
`build/distribuions/spatial-showcase-js-0.1.3-dev/index.html` file in your browser.
|
||||
|
||||
To see Java FX demo, run `demo/spatial-showcase/Tasks/application/run` Gradle task, or `main()` from `FXDemoApp.kt`.
|
||||
|
||||
##### Example view for JS:
|
||||
|
||||
![](../../doc/resources/spatial-showcase.png)
|
||||
|
||||
##### Example view for Java FX:
|
||||
|
||||
![](../../doc/resources/spatial-showcase-FX.png)
|
@ -1,6 +1,6 @@
|
||||
import scientifik.DependencyConfiguration
|
||||
import scientifik.FXModule
|
||||
import scientifik.fx
|
||||
import scientifik.useFx
|
||||
|
||||
plugins {
|
||||
id("scientifik.mpp")
|
||||
@ -8,7 +8,7 @@ plugins {
|
||||
}
|
||||
|
||||
val fxVersion: String by rootProject.extra
|
||||
fx(FXModule.CONTROLS, version = fxVersion, configuration = DependencyConfiguration.IMPLEMENTATION)
|
||||
useFx(FXModule.CONTROLS, version = fxVersion, configuration = DependencyConfiguration.IMPLEMENTATION)
|
||||
|
||||
kotlin {
|
||||
|
||||
@ -19,13 +19,13 @@ kotlin {
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api(project(":dataforge-vis-spatial"))
|
||||
api(project(":dataforge-vis-spatial-gdml"))
|
||||
api(project(":visionforge-solid"))
|
||||
api(project(":visionforge-gdml"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
application {
|
||||
mainClassName = "hep.dataforge.vis.spatial.demo.FXDemoAppKt"
|
||||
mainClassName = "hep.dataforge.vision.solid.demo.FXDemoAppKt"
|
||||
}
|
@ -1,13 +1,13 @@
|
||||
package hep.dataforge.vis.spatial.demo
|
||||
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.vis.Colors
|
||||
import hep.dataforge.vis.VisualObject
|
||||
import hep.dataforge.vis.spatial.*
|
||||
import hep.dataforge.vis.spatial.specifications.Canvas
|
||||
import hep.dataforge.vision.Colors
|
||||
import hep.dataforge.vision.Vision
|
||||
import hep.dataforge.vision.solid.*
|
||||
import hep.dataforge.vision.solid.specifications.Canvas3DOptions
|
||||
import kotlinx.coroutines.*
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.cos
|
||||
@ -15,15 +15,15 @@ import kotlin.math.sin
|
||||
import kotlin.random.Random
|
||||
|
||||
|
||||
fun OutputManager.demo(name: String, title: String = name, block: VisualGroup3D.() -> Unit) {
|
||||
fun OutputManager.demo(name: String, title: String = name, block: SolidGroup.() -> Unit) {
|
||||
val meta = Meta {
|
||||
"title" put title
|
||||
}
|
||||
val output = get(VisualObject::class, name.toName(), meta = meta)
|
||||
output.render(action = block)
|
||||
val output = get(Vision::class, name.toName(), meta = meta)
|
||||
output.render (action = block)
|
||||
}
|
||||
|
||||
val canvasOptions = Canvas {
|
||||
val canvasOptions = Canvas3DOptions {
|
||||
minSize = 500
|
||||
axes {
|
||||
size = 500.0
|
@ -1,4 +1,4 @@
|
||||
package hep.dataforge.vis.spatial.demo
|
||||
package hep.dataforge.vision.solid.demo
|
||||
|
||||
import hep.dataforge.js.Application
|
||||
import hep.dataforge.js.startApplication
|
@ -1,4 +1,4 @@
|
||||
package hep.dataforge.vis.spatial.demo
|
||||
package hep.dataforge.vision.solid.demo
|
||||
|
||||
import hep.dataforge.context.Global
|
||||
import hep.dataforge.meta.Meta
|
||||
@ -7,10 +7,10 @@ import hep.dataforge.meta.string
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.output.OutputManager
|
||||
import hep.dataforge.output.Renderer
|
||||
import hep.dataforge.vis.VisualObject
|
||||
import hep.dataforge.vis.spatial.three.ThreeCanvas
|
||||
import hep.dataforge.vis.spatial.three.ThreePlugin
|
||||
import hep.dataforge.vis.spatial.three.output
|
||||
import hep.dataforge.vision.Vision
|
||||
import hep.dataforge.vision.solid.three.ThreeCanvas
|
||||
import hep.dataforge.vision.solid.three.ThreePlugin
|
||||
import hep.dataforge.vision.solid.three.output
|
||||
import kotlinx.html.dom.append
|
||||
import kotlinx.html.dom.create
|
||||
import kotlinx.html.h2
|
||||
@ -39,7 +39,7 @@ class ThreeDemoGrid(element: Element, meta: Meta = Meta.EMPTY) : OutputManager {
|
||||
override fun <T : Any> get(type: KClass<out T>, name: Name, stage: Name, meta: Meta): Renderer<T> {
|
||||
|
||||
return outputs.getOrPut(name) {
|
||||
if (type != VisualObject::class) error("Supports only DisplayObject")
|
||||
if (type != Vision::class) error("Supports only DisplayObject")
|
||||
lateinit var output: ThreeCanvas
|
||||
//TODO calculate cell width here using jquery
|
||||
gridRoot.append {
|
@ -1,20 +1,20 @@
|
||||
@file:UseSerializers(Point3DSerializer::class)
|
||||
|
||||
package hep.dataforge.vis.spatial.demo
|
||||
package hep.dataforge.vision.solid.demo
|
||||
|
||||
import hep.dataforge.meta.int
|
||||
import hep.dataforge.meta.number
|
||||
import hep.dataforge.meta.setProperty
|
||||
import hep.dataforge.meta.setItem
|
||||
import hep.dataforge.names.plus
|
||||
import hep.dataforge.names.startsWith
|
||||
import hep.dataforge.values.asValue
|
||||
import hep.dataforge.vis.getProperty
|
||||
import hep.dataforge.vis.set
|
||||
import hep.dataforge.vis.spatial.*
|
||||
import hep.dataforge.vis.spatial.VisualObject3D.Companion.GEOMETRY_KEY
|
||||
import hep.dataforge.vis.spatial.demo.VariableBoxThreeFactory.Z_SIZE_KEY
|
||||
import hep.dataforge.vis.spatial.three.*
|
||||
import hep.dataforge.vis.spatial.three.ThreeMaterials.getMaterial
|
||||
import hep.dataforge.vision.getProperty
|
||||
import hep.dataforge.vision.set
|
||||
import hep.dataforge.vision.solid.*
|
||||
import hep.dataforge.vision.solid.Solid.Companion.GEOMETRY_KEY
|
||||
import hep.dataforge.vision.solid.demo.VariableBoxThreeFactory.Z_SIZE_KEY
|
||||
import hep.dataforge.vision.solid.three.*
|
||||
import hep.dataforge.vision.solid.three.ThreeMaterials.getMaterial
|
||||
import info.laht.threekt.core.BufferGeometry
|
||||
import info.laht.threekt.core.Object3D
|
||||
import info.laht.threekt.geometries.BoxBufferGeometry
|
||||
@ -23,16 +23,16 @@ import kotlinx.serialization.UseSerializers
|
||||
import kotlin.math.max
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
internal var VisualObject3D.variableZSize: Number
|
||||
internal var Solid.variableZSize: Number
|
||||
get() = getProperty(Z_SIZE_KEY, false).number ?: 0f
|
||||
set(value) {
|
||||
setProperty(Z_SIZE_KEY, value.asValue())
|
||||
setItem(Z_SIZE_KEY, value.asValue())
|
||||
}
|
||||
|
||||
internal var VisualObject3D.value: Int
|
||||
internal var Solid.value: Int
|
||||
get() = getProperty("value", false).int ?: 0
|
||||
set(value) {
|
||||
setProperty("value", value.asValue())
|
||||
setItem("value", value.asValue())
|
||||
val size = value.toFloat() / 255f * 20f
|
||||
scaleZ = size
|
||||
z = -size / 2
|
||||
@ -43,26 +43,26 @@ internal var VisualObject3D.value: Int
|
||||
color(r.toUByte(), g.toUByte(), b.toUByte())
|
||||
}
|
||||
|
||||
fun VisualGroup3D.varBox(
|
||||
fun SolidGroup.varBox(
|
||||
xSize: Number,
|
||||
ySize: Number,
|
||||
zSize: Number,
|
||||
name: String = "",
|
||||
action: VisualObject3D.() -> Unit = {}
|
||||
) = CustomThreeVisualObject(VariableBoxThreeFactory).apply {
|
||||
action: Solid.() -> Unit = {}
|
||||
) = CustomThreeVision(VariableBoxThreeFactory).apply {
|
||||
scaleX = xSize
|
||||
scaleY = ySize
|
||||
scaleZ = zSize
|
||||
}.apply(action).also { set(name, it) }
|
||||
|
||||
private object VariableBoxThreeFactory : ThreeFactory<VisualObject3D> {
|
||||
private object VariableBoxThreeFactory : ThreeFactory<Solid> {
|
||||
val X_SIZE_KEY = GEOMETRY_KEY + "xSize"
|
||||
val Y_SIZE_KEY = GEOMETRY_KEY + "ySize"
|
||||
val Z_SIZE_KEY = GEOMETRY_KEY + "zSize"
|
||||
|
||||
override val type: KClass<in VisualObject3D> get() = VisualObject3D::class
|
||||
override val type: KClass<in Solid> get() = Solid::class
|
||||
|
||||
override fun invoke(obj: VisualObject3D): Object3D {
|
||||
override fun invoke(obj: Solid): Object3D {
|
||||
val xSize = obj.getProperty(X_SIZE_KEY, false).number?.toDouble() ?: 1.0
|
||||
val ySize = obj.getProperty(Y_SIZE_KEY, false).number?.toDouble() ?: 1.0
|
||||
val zSize = obj.getProperty(Z_SIZE_KEY, false).number?.toDouble() ?: 1.0
|
||||
@ -87,7 +87,7 @@ private object VariableBoxThreeFactory : ThreeFactory<VisualObject3D> {
|
||||
mesh.scale.set(xSize, ySize, zSize)
|
||||
|
||||
//add listener to object properties
|
||||
obj.onPropertyChange(this) { name, _, _ ->
|
||||
obj.onPropertyChange(this) { name ->
|
||||
when {
|
||||
// name.startsWith(GEOMETRY_KEY) -> {
|
||||
// val newXSize = obj.getProperty(X_SIZE_KEY, false).number?.toDouble() ?: 1.0
|
@ -8,7 +8,7 @@
|
||||
integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
|
||||
<script type="text/javascript" src="main.bundle.js"></script>
|
||||
</head>
|
||||
<body class="testApp">
|
||||
<body class="application">
|
||||
<div class="container">
|
||||
<h1>Demo grid</h1>
|
||||
</div>
|
||||
|
@ -1,28 +0,0 @@
|
||||
package hep.dataforge.vis.spatial.demo
|
||||
|
||||
import hep.dataforge.vis.spatial.gdml.gdml
|
||||
import javafx.stage.Stage
|
||||
import tornadofx.*
|
||||
import java.nio.file.Paths
|
||||
|
||||
class FXDemoApp : App(FXDemoGrid::class) {
|
||||
|
||||
val view: FXDemoGrid by inject()
|
||||
|
||||
override fun start(stage: Stage) {
|
||||
super.start(stage)
|
||||
|
||||
stage.width = 600.0
|
||||
stage.height = 600.0
|
||||
|
||||
view.showcase()
|
||||
view.demo("gdml", "gdml-cubes") {
|
||||
gdml(Paths.get("D:\\Work\\Projects\\gdml.kt\\gdml-source\\cubes.gdml"))
|
||||
//setProperty(Material3D.MATERIAL_WIREFRAME_KEY, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun main() {
|
||||
launch<FXDemoApp>()
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package hep.dataforge.vision.solid.demo
|
||||
|
||||
import hep.dataforge.vision.gdml.gdml
|
||||
import javafx.stage.Stage
|
||||
import tornadofx.*
|
||||
import java.nio.file.NoSuchFileException
|
||||
import java.nio.file.Paths
|
||||
|
||||
class FXDemoApp : App(FXDemoGrid::class) {
|
||||
|
||||
val view: FXDemoGrid by inject()
|
||||
|
||||
override fun start(stage: Stage) {
|
||||
super.start(stage)
|
||||
|
||||
stage.width = 600.0
|
||||
stage.height = 600.0
|
||||
|
||||
view.showcase()
|
||||
try {
|
||||
view.demo("gdml", "gdml-cubes") {
|
||||
gdml(Paths.get("D:\\Work\\Projects\\gdml.kt\\gdml-source\\cubes.gdml"))
|
||||
//setProperty(Material3D.MATERIAL_WIREFRAME_KEY, true)
|
||||
}
|
||||
}
|
||||
catch (e: NoSuchFileException) {
|
||||
println("GDML demo: Please specify the correct file path e.g. " +
|
||||
"visionforge\\demo\\gdml\\src\\commonMain\\resources\\cubes.gdml")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun main() {
|
||||
launch<FXDemoApp>()
|
||||
}
|
@ -1,16 +1,13 @@
|
||||
package hep.dataforge.vis.spatial.demo
|
||||
package hep.dataforge.vision.solid.demo
|
||||
|
||||
import hep.dataforge.context.Global
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.meta.buildMeta
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.output.OutputManager
|
||||
import hep.dataforge.output.Renderer
|
||||
import hep.dataforge.vis.VisualObject
|
||||
import hep.dataforge.vis.spatial.fx.FX3DPlugin
|
||||
import hep.dataforge.vis.spatial.fx.FXCanvas3D
|
||||
import hep.dataforge.vis.spatial.specifications.Axes as AxesSpec
|
||||
import hep.dataforge.vis.spatial.specifications.Canvas as CanvasSpec
|
||||
import hep.dataforge.vision.Vision
|
||||
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
|
||||
@ -33,7 +30,7 @@ class FXDemoGrid : View(title = "DataForge-vis FX demo"), OutputManager {
|
||||
@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 != VisualObject::class) kotlin.error("Supports only DisplayObject")
|
||||
if (type != Vision::class) kotlin.error("Supports only DisplayObject")
|
||||
val output = FXCanvas3D(fx3d, canvasOptions)
|
||||
|
||||
output
|
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 6.0 KiB |
BIN
doc/resources/class-diag-core.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
doc/resources/class-diag-solid.png
Normal file
After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 87 KiB |
BIN
doc/resources/spatial-showcase-FX.png
Normal file
After Width: | Height: | Size: 28 KiB |
@ -19,8 +19,9 @@ kotlin {
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api(project(":dataforge-vis-spatial"))
|
||||
api(project(":dataforge-vis-spatial-gdml"))
|
||||
api(project(":visionforge-solid"))
|
||||
api(project(":visionforge-gdml"))
|
||||
api(project(":ui:bootstrap"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,14 +2,14 @@ import hep.dataforge.context.Global
|
||||
import hep.dataforge.js.Application
|
||||
import hep.dataforge.js.startApplication
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.vis.editor.objectTree
|
||||
import hep.dataforge.vis.editor.visualPropertyEditor
|
||||
import hep.dataforge.vis.spatial.Point3D
|
||||
import hep.dataforge.vis.spatial.VisualGroup3D
|
||||
import hep.dataforge.vis.spatial.box
|
||||
import hep.dataforge.vis.spatial.group
|
||||
import hep.dataforge.vis.spatial.three.ThreePlugin
|
||||
import hep.dataforge.vis.spatial.three.threeCanvas
|
||||
import hep.dataforge.vision.bootstrap.objectTree
|
||||
import hep.dataforge.vision.bootstrap.visualPropertyEditor
|
||||
import hep.dataforge.vision.solid.Point3D
|
||||
import hep.dataforge.vision.solid.SolidGroup
|
||||
import hep.dataforge.vision.solid.box
|
||||
import hep.dataforge.vision.solid.group
|
||||
import hep.dataforge.vision.solid.three.ThreePlugin
|
||||
import hep.dataforge.vision.solid.three.threeCanvas
|
||||
import org.w3c.dom.HTMLElement
|
||||
import react.dom.div
|
||||
import react.dom.render
|
||||
@ -24,7 +24,7 @@ private class PlayGroundApp : Application {
|
||||
val element =
|
||||
document.getElementById("app") as? HTMLElement ?: error("Element with id 'canvas' not found on page")
|
||||
|
||||
val obj = VisualGroup3D().apply {
|
||||
val obj = SolidGroup().apply {
|
||||
box(100, 100, 100, name = "A")
|
||||
group("B") {
|
||||
position = Point3D(120, 0, 0)
|
||||
@ -45,8 +45,6 @@ private class PlayGroundApp : Application {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
<link rel="stylesheet" href="css/common.css">
|
||||
<script type="text/javascript" src="playground.js"></script>
|
||||
</head>
|
||||
<body class="testApp">
|
||||
<body class="application">
|
||||
<div class="container">
|
||||
<h1>Playground</h1>
|
||||
</div>
|
||||
|
@ -1,4 +1,7 @@
|
||||
pluginManagement {
|
||||
val kotlinVersion = "1.3.72"
|
||||
val toolsVersion = "0.5.2"
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
jcenter()
|
||||
@ -10,11 +13,18 @@ pluginManagement {
|
||||
maven("https://dl.bintray.com/mipt-npm/dev")
|
||||
}
|
||||
|
||||
plugins {
|
||||
kotlin("jvm") version kotlinVersion
|
||||
id("scientifik.mpp") version toolsVersion
|
||||
id("scientifik.jvm") version toolsVersion
|
||||
id("scientifik.js") version toolsVersion
|
||||
id("scientifik.publish") version toolsVersion
|
||||
}
|
||||
|
||||
resolutionStrategy {
|
||||
eachPlugin {
|
||||
when (requested.id.id) {
|
||||
"scientifik.mpp", "scientifik.publish", "scientifik.jvm", "scientifik.js" -> useModule("scientifik:gradle-tools:${requested.version}")
|
||||
"org.openjfx.javafxplugin" -> useModule("org.openjfx:javafx-plugin:${requested.version}")
|
||||
"scientifik.mpp", "scientifik.publish", "scientifik.jvm", "scientifik.js" -> useModule("scientifik:gradle-tools:${toolsVersion}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -22,24 +32,19 @@ pluginManagement {
|
||||
|
||||
//enableFeaturePreview("GRADLE_METADATA")
|
||||
|
||||
rootProject.name = "dataforge-vis"
|
||||
rootProject.name = "visionforge"
|
||||
|
||||
include(
|
||||
":dataforge-vis-common",
|
||||
":dataforge-vis-spatial",
|
||||
":dataforge-vis-spatial-gdml",
|
||||
":ui",
|
||||
":ui:react",
|
||||
":ui:ring",
|
||||
":ui:material",
|
||||
":ui:bootstrap",
|
||||
":visionforge-core",
|
||||
":visionforge-solid",
|
||||
":visionforge-gdml",
|
||||
":demo:spatial-showcase",
|
||||
":demo:gdml",
|
||||
":demo:muon-monitor",
|
||||
":playground"
|
||||
)
|
||||
|
||||
//if(file("../dataforge-core").exists()) {
|
||||
// includeBuild("../dataforge-core"){
|
||||
// dependencySubstitution {
|
||||
// //substitute(module("hep.dataforge:dataforge-output")).with(project(":dataforge-output"))
|
||||
// substitute(module("hep.dataforge:dataforge-output-jvm")).with(project(":dataforge-output"))
|
||||
// substitute(module("hep.dataforge:dataforge-output-js")).with(project(":dataforge-output"))
|
||||
// }
|
||||
// }
|
||||
//}
|
15
ui/bootstrap/build.gradle.kts
Normal file
@ -0,0 +1,15 @@
|
||||
plugins {
|
||||
id("scientifik.js")
|
||||
}
|
||||
|
||||
val dataforgeVersion: String by rootProject.extra
|
||||
|
||||
kotlin {
|
||||
target {
|
||||
useCommonJs()
|
||||
}
|
||||
}
|
||||
|
||||
dependencies{
|
||||
api(project(":ui:react"))
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package hep.dataforge.vis.editor
|
||||
package hep.dataforge.vision.bootstrap
|
||||
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.meta.MetaItem
|
||||
@ -34,10 +34,10 @@ class MetaViewerComponent : RComponent<MetaViewerProps, TreeState>() {
|
||||
override fun RBuilder.render() {
|
||||
div("d-inline-block text-truncate") {
|
||||
if (props.meta.items.isNotEmpty()) {
|
||||
span("objTree-caret") {
|
||||
span("tree-caret") {
|
||||
attrs {
|
||||
if (state.expanded) {
|
||||
classes += "objTree-caret-down"
|
||||
classes += "tree-caret-down"
|
||||
}
|
||||
onClickFunction = onClick
|
||||
}
|
@ -0,0 +1,127 @@
|
||||
package hep.dataforge.vision.bootstrap
|
||||
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.plus
|
||||
import hep.dataforge.names.startsWith
|
||||
import hep.dataforge.vision.Vision
|
||||
import hep.dataforge.vision.VisionGroup
|
||||
import hep.dataforge.vision.isEmpty
|
||||
import hep.dataforge.vision.react.RFBuilder
|
||||
import hep.dataforge.vision.react.component
|
||||
import kotlinx.html.classes
|
||||
import kotlinx.html.js.onClickFunction
|
||||
import org.w3c.dom.Element
|
||||
import org.w3c.dom.events.Event
|
||||
import react.*
|
||||
import react.dom.*
|
||||
|
||||
interface ObjectTreeProps : RProps {
|
||||
var name: Name
|
||||
var selected: Name?
|
||||
var obj: Vision
|
||||
var clickCallback: (Name) -> Unit
|
||||
}
|
||||
|
||||
interface TreeState : RState {
|
||||
var expanded: Boolean
|
||||
}
|
||||
|
||||
private fun RFBuilder.objectTree(props: ObjectTreeProps): Unit {
|
||||
var expanded: Boolean by useState{ props.selected?.startsWith(props.name) ?: false }
|
||||
|
||||
val onClick: (Event) -> Unit = {
|
||||
expanded = !expanded
|
||||
}
|
||||
|
||||
fun RBuilder.treeLabel(text: String) {
|
||||
button(classes = "btn btn-link align-middle tree-label p-0") {
|
||||
+text
|
||||
attrs {
|
||||
if (props.name == props.selected) {
|
||||
classes += "tree-label-selected"
|
||||
}
|
||||
onClickFunction = { props.clickCallback(props.name) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val token = props.name.last()?.toString() ?: "World"
|
||||
val obj = props.obj
|
||||
|
||||
//display as node if any child is visible
|
||||
if (obj is VisionGroup) {
|
||||
div("d-block text-truncate") {
|
||||
if (obj.children.any { !it.key.body.startsWith("@") }) {
|
||||
span("tree-caret") {
|
||||
attrs {
|
||||
if (expanded) {
|
||||
classes += "tree-caret-down"
|
||||
}
|
||||
onClickFunction = onClick
|
||||
}
|
||||
}
|
||||
}
|
||||
treeLabel(token)
|
||||
}
|
||||
if (expanded) {
|
||||
ul("tree") {
|
||||
obj.children.entries
|
||||
.filter { !it.key.toString().startsWith("@") } // ignore statics and other hidden children
|
||||
.sortedBy { (it.value as? VisionGroup)?.isEmpty ?: true }
|
||||
.forEach { (childToken, child) ->
|
||||
li("tree-item") {
|
||||
child(ObjectTree) {
|
||||
attrs {
|
||||
this.name = props.name + childToken
|
||||
this.obj = child
|
||||
this.selected = props.selected
|
||||
this.clickCallback = props.clickCallback
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
div("d-block text-truncate") {
|
||||
span("tree-leaf") {}
|
||||
treeLabel(token)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val ObjectTree: FunctionalComponent<ObjectTreeProps> = component { props ->
|
||||
objectTree(props)
|
||||
}
|
||||
|
||||
fun Element.renderObjectTree(
|
||||
vision: Vision,
|
||||
clickCallback: (Name) -> Unit = {}
|
||||
) = render(this) {
|
||||
card("Object tree") {
|
||||
child(ObjectTree) {
|
||||
attrs {
|
||||
this.name = Name.EMPTY
|
||||
this.obj = vision
|
||||
this.selected = null
|
||||
this.clickCallback = clickCallback
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun RBuilder.objectTree(
|
||||
vision: Vision,
|
||||
selected: Name? = null,
|
||||
clickCallback: (Name) -> Unit = {}
|
||||
) {
|
||||
child(ObjectTree) {
|
||||
attrs {
|
||||
this.name = Name.EMPTY
|
||||
this.obj = vision
|
||||
this.selected = selected
|
||||
this.clickCallback = clickCallback
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,208 @@
|
||||
package hep.dataforge.vision.bootstrap
|
||||
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.NameToken
|
||||
import kotlinx.html.*
|
||||
import kotlinx.html.js.div
|
||||
import kotlinx.html.js.onClickFunction
|
||||
import org.w3c.dom.HTMLElement
|
||||
import react.RBuilder
|
||||
import react.ReactElement
|
||||
import react.dom.*
|
||||
|
||||
inline fun TagConsumer<HTMLElement>.card(title: String, crossinline block: TagConsumer<HTMLElement>.() -> Unit) {
|
||||
div("card w-100") {
|
||||
div("card-body") {
|
||||
h3(classes = "card-title") { +title }
|
||||
block()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline fun RBuilder.card(title: String, classes: String? = null, crossinline block: RBuilder.() -> Unit) {
|
||||
div("card w-100 $classes") {
|
||||
div("card-body") {
|
||||
h3(classes = "card-title") {
|
||||
+title
|
||||
}
|
||||
block()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun TagConsumer<HTMLElement>.accordion(id: String, elements: List<Pair<String, DIV.() -> Unit>>) {
|
||||
div("container-fluid") {
|
||||
div("accordion") {
|
||||
this.id = id
|
||||
elements.forEachIndexed { index, (title, builder) ->
|
||||
val headerID = "${id}-${index}-heading"
|
||||
val collapseID = "${id}-${index}-collapse"
|
||||
div("card") {
|
||||
div("card-header") {
|
||||
this.id = headerID
|
||||
h5("mb-0") {
|
||||
button(classes = "btn btn-link collapsed", type = ButtonType.button) {
|
||||
attributes["data-toggle"] = "collapse"
|
||||
attributes["data-target"] = "#$collapseID"
|
||||
attributes["aria-expanded"] = "false"
|
||||
attributes["aria-controls"] = collapseID
|
||||
+title
|
||||
}
|
||||
}
|
||||
}
|
||||
div("collapse") {
|
||||
this.id = collapseID
|
||||
attributes["aria-labelledby"] = headerID
|
||||
attributes["data-parent"] = "#$id"
|
||||
div("card-body", block = builder)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typealias AccordionBuilder = MutableList<Pair<String, DIV.() -> Unit>>
|
||||
|
||||
fun AccordionBuilder.entry(title: String, builder: DIV.() -> Unit) {
|
||||
add(title to builder)
|
||||
}
|
||||
|
||||
fun TagConsumer<HTMLElement>.accordion(id: String, builder: AccordionBuilder.() -> Unit) {
|
||||
val list = ArrayList<Pair<String, DIV.() -> Unit>>().apply(builder)
|
||||
accordion(id, list)
|
||||
}
|
||||
|
||||
fun RBuilder.accordion(id: String, elements: List<Pair<String, RDOMBuilder<DIV>.() -> Unit>>): ReactElement {
|
||||
return div("container-fluid") {
|
||||
div("accordion") {
|
||||
attrs {
|
||||
this.id = id
|
||||
}
|
||||
elements.forEachIndexed { index, (title, builder) ->
|
||||
val headerID = "${id}-${index}-heading"
|
||||
val collapseID = "${id}-${index}-collapse"
|
||||
div("card p-0 m-0") {
|
||||
div("card-header") {
|
||||
attrs {
|
||||
this.id = headerID
|
||||
}
|
||||
h5("mb-0") {
|
||||
button(classes = "btn btn-link collapsed", type = ButtonType.button) {
|
||||
attrs {
|
||||
attributes["data-toggle"] = "collapse"
|
||||
attributes["data-target"] = "#$collapseID"
|
||||
attributes["aria-expanded"] = "false"
|
||||
attributes["aria-controls"] = collapseID
|
||||
}
|
||||
+title
|
||||
}
|
||||
}
|
||||
}
|
||||
div("collapse") {
|
||||
attrs {
|
||||
this.id = collapseID
|
||||
attributes["aria-labelledby"] = headerID
|
||||
attributes["data-parent"] = "#$id"
|
||||
}
|
||||
div("card-body", block = builder)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun RBuilder.namecrumbs(name: Name?, rootTitle: String, link: (Name) -> Unit) {
|
||||
div("container-fluid p-0") {
|
||||
nav {
|
||||
attrs {
|
||||
attributes["aria-label"] = "breadcrumb"
|
||||
}
|
||||
ol("breadcrumb") {
|
||||
li("breadcrumb-item") {
|
||||
button(classes = "btn btn-link p-0") {
|
||||
+rootTitle
|
||||
attrs {
|
||||
onClickFunction = {
|
||||
link(Name.EMPTY)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (name != null) {
|
||||
val tokens = ArrayList<NameToken>(name.length)
|
||||
name.tokens.forEach { token ->
|
||||
tokens.add(token)
|
||||
val fullName = Name(tokens.toList())
|
||||
li("breadcrumb-item") {
|
||||
button(classes = "btn btn-link p-0") {
|
||||
+token.toString()
|
||||
attrs {
|
||||
onClickFunction = {
|
||||
console.log("Selected = $fullName")
|
||||
link(fullName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typealias RAccordionBuilder = MutableList<Pair<String, RDOMBuilder<DIV>.() -> Unit>>
|
||||
|
||||
fun RAccordionBuilder.entry(title: String, builder: RDOMBuilder<DIV>.() -> Unit) {
|
||||
add(title to builder)
|
||||
}
|
||||
|
||||
fun RBuilder.accordion(id: String, builder: RAccordionBuilder.() -> Unit): ReactElement {
|
||||
val list = ArrayList<Pair<String, RDOMBuilder<DIV>.() -> Unit>>().apply(builder)
|
||||
return accordion(id, list)
|
||||
}
|
||||
|
||||
fun joinStyles(vararg styles: String?) = styles.joinToString(separator = " ") { it ?: "" }
|
||||
|
||||
enum class ContainerSize(val suffix: String) {
|
||||
DEFAULT(""),
|
||||
SM("-sm"),
|
||||
MD("-md"),
|
||||
LG("-lg"),
|
||||
XL("-xl"),
|
||||
FLUID("-fluid")
|
||||
}
|
||||
|
||||
inline fun RBuilder.container(
|
||||
classes: String? = null,
|
||||
size: ContainerSize = ContainerSize.FLUID,
|
||||
block: RDOMBuilder<DIV>.() -> Unit
|
||||
): ReactElement = div(joinStyles(classes, "container${size.suffix}"), block)
|
||||
|
||||
|
||||
enum class GridMaxSize(val suffix: String) {
|
||||
NONE(""),
|
||||
SM("-sm"),
|
||||
MD("-md"),
|
||||
LG("-lg"),
|
||||
XL("-xl")
|
||||
}
|
||||
|
||||
inline fun RBuilder.gridColumn(
|
||||
weight: Int? = null,
|
||||
classes: String? = null,
|
||||
maxSize: GridMaxSize = GridMaxSize.NONE,
|
||||
block: RDOMBuilder<DIV>.() -> Unit
|
||||
): ReactElement {
|
||||
val weightSuffix = weight?.let { "-$it" } ?: ""
|
||||
return div(joinStyles(classes, "col${maxSize.suffix}$weightSuffix"), block)
|
||||
}
|
||||
|
||||
inline fun RBuilder.gridRow(
|
||||
classes: String? = null,
|
||||
block: RDOMBuilder<DIV>.() -> Unit
|
||||
): ReactElement {
|
||||
return div(joinStyles(classes, "row"), block)
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package hep.dataforge.vision.bootstrap
|
||||
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.meta.descriptors.NodeDescriptor
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.isEmpty
|
||||
import hep.dataforge.vision.Vision
|
||||
import hep.dataforge.vision.react.configEditor
|
||||
import org.w3c.dom.Element
|
||||
import react.RBuilder
|
||||
import react.dom.li
|
||||
import react.dom.nav
|
||||
import react.dom.ol
|
||||
import react.dom.render
|
||||
import kotlin.collections.set
|
||||
|
||||
fun RBuilder.visualPropertyEditor(
|
||||
path: Name,
|
||||
item: Vision,
|
||||
descriptor: NodeDescriptor? = item.descriptor,
|
||||
default: Meta? = null
|
||||
) {
|
||||
card("Properties") {
|
||||
if (!path.isEmpty()) {
|
||||
nav {
|
||||
attrs {
|
||||
attributes["aria-label"] = "breadcrumb"
|
||||
}
|
||||
ol("breadcrumb") {
|
||||
path.tokens.forEach { token ->
|
||||
li("breadcrumb-item") {
|
||||
+token.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
configEditor(item, descriptor, default)
|
||||
}
|
||||
}
|
||||
|
||||
fun Element.visualPropertyEditor(
|
||||
path: Name,
|
||||
item: Vision,
|
||||
descriptor: NodeDescriptor? = item.descriptor,
|
||||
default: Meta? = null
|
||||
) = render(this) {
|
||||
this.visualPropertyEditor(path, item, descriptor, default)
|
||||
}
|
7
ui/bootstrap/src/main/resources/css/bootstrap.min.css
vendored
Normal file
@ -1,3 +1,11 @@
|
||||
/*full height*/
|
||||
html, body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.container-fluid { height: inherit; }
|
||||
|
||||
/* Remove default bullets */
|
||||
ul, .tree {
|
||||
list-style-type: none;
|
||||
@ -36,5 +44,13 @@ ul, .tree {
|
||||
}
|
||||
|
||||
.tree-label-inactive {
|
||||
color: gray;
|
||||
color: lightgrey;
|
||||
}
|
||||
|
||||
.tree-label-selected{
|
||||
background-color: lightblue;
|
||||
}
|
||||
|
||||
.no-padding{
|
||||
padding: 0;
|
||||
}
|
7
ui/bootstrap/src/main/resources/js/bootstrap.bundle.min.js
vendored
Normal file
2
ui/bootstrap/src/main/resources/js/jquery-3.4.1.min.js
vendored
Normal file
0
ui/build.gradle.kts
Normal file
21
ui/material/build.gradle.kts
Normal file
@ -0,0 +1,21 @@
|
||||
plugins {
|
||||
id("scientifik.js")
|
||||
}
|
||||
|
||||
val dataforgeVersion: String by rootProject.extra
|
||||
|
||||
kotlin {
|
||||
target {
|
||||
useCommonJs()
|
||||
}
|
||||
}
|
||||
|
||||
dependencies{
|
||||
api(project(":ui:react"))
|
||||
|
||||
api("subroh0508.net.kotlinmaterialui:core:0.4.0")
|
||||
api("subroh0508.net.kotlinmaterialui:lab:0.4.0")
|
||||
api(npm("@material-ui/core","4.9.14"))
|
||||
api(npm("@material-ui/lab","4.0.0-alpha.51"))
|
||||
//api(npm("@material-ui/icons","4.9.1"))
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package hep.dataforge.vision.material
|
||||
|
||||
//@JsModule("@material-ui/icons/ExpandMore")
|
||||
//external class ExpandMoreIcon : Component<RProps, RState>{
|
||||
// override fun render(): dynamic
|
||||
//}
|
||||
//
|
||||
//@JsModule("@material-ui/icons/ChevronRight")
|
||||
//external class ChevronRightIcon : Component<RProps, RState>{
|
||||
// override fun render(): dynamic
|
||||
//}
|
@ -0,0 +1,97 @@
|
||||
package hep.dataforge.vision.material
|
||||
|
||||
import hep.dataforge.vision.react.component
|
||||
import hep.dataforge.vision.react.state
|
||||
import kotlinx.html.DIV
|
||||
import materialui.components.card.card
|
||||
import materialui.components.cardcontent.cardContent
|
||||
import materialui.components.cardheader.cardHeader
|
||||
import materialui.components.container.container
|
||||
import materialui.components.container.enums.ContainerMaxWidth
|
||||
import materialui.components.expansionpanel.expansionPanel
|
||||
import materialui.components.expansionpaneldetails.expansionPanelDetails
|
||||
import materialui.components.expansionpanelsummary.expansionPanelSummary
|
||||
import materialui.components.grid.GridElementBuilder
|
||||
import materialui.components.grid.enums.GridDirection
|
||||
import materialui.components.grid.enums.GridStyle
|
||||
import materialui.components.grid.grid
|
||||
import materialui.components.paper.paper
|
||||
import materialui.components.typography.typographyH5
|
||||
import react.RBuilder
|
||||
import react.RProps
|
||||
import react.child
|
||||
import react.dom.RDOMBuilder
|
||||
|
||||
|
||||
fun accordionComponent(elements: List<Pair<String, RDOMBuilder<DIV>.() -> Unit>>) =
|
||||
component<RProps> {
|
||||
val expandedIndex: Int? by state { null }
|
||||
|
||||
container {
|
||||
attrs {
|
||||
maxWidth = ContainerMaxWidth.`false`
|
||||
}
|
||||
elements.forEachIndexed { index, (header, body) ->
|
||||
expansionPanel {
|
||||
attrs {
|
||||
expanded = index == expandedIndex
|
||||
}
|
||||
expansionPanelSummary {
|
||||
typographyH5 {
|
||||
+header
|
||||
}
|
||||
}
|
||||
expansionPanelDetails {
|
||||
this.body()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typealias RAccordionBuilder = MutableList<Pair<String, RDOMBuilder<DIV>.() -> Unit>>
|
||||
|
||||
fun RAccordionBuilder.entry(title: String, builder: RDOMBuilder<DIV>.() -> Unit) {
|
||||
add(title to builder)
|
||||
}
|
||||
|
||||
fun RBuilder.accordion(builder: RAccordionBuilder.() -> Unit) {
|
||||
val list: List<Pair<String, RDOMBuilder<DIV>.() -> Unit>> =
|
||||
ArrayList<Pair<String, RDOMBuilder<DIV>.() -> Unit>>().apply(builder)
|
||||
child(accordionComponent(list))
|
||||
}
|
||||
|
||||
fun RBuilder.materialCard(title: String, block: RBuilder.() -> Unit) {
|
||||
card {
|
||||
cardHeader {
|
||||
attrs {
|
||||
this.title = typographyH5 {
|
||||
+title
|
||||
}
|
||||
}
|
||||
}
|
||||
cardContent {
|
||||
paper {
|
||||
this.block()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun RBuilder.column(vararg classMap: Pair<GridStyle, String>, block: GridElementBuilder<DIV>.() -> Unit) =
|
||||
grid(*classMap) {
|
||||
attrs {
|
||||
container = true
|
||||
direction = GridDirection.column
|
||||
}
|
||||
block()
|
||||
}
|
||||
|
||||
fun RBuilder.row(vararg classMap: Pair<GridStyle, String>, block: GridElementBuilder<DIV>.() -> Unit) =
|
||||
grid(*classMap) {
|
||||
attrs {
|
||||
container = true
|
||||
direction = GridDirection.row
|
||||
}
|
||||
block()
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
package hep.dataforge.vision.material
|
||||
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.plus
|
||||
import hep.dataforge.names.toName
|
||||
import hep.dataforge.vision.Vision
|
||||
import hep.dataforge.vision.VisionGroup
|
||||
import hep.dataforge.vision.isEmpty
|
||||
import hep.dataforge.vision.react.component
|
||||
import hep.dataforge.vision.react.state
|
||||
import kotlinx.html.UL
|
||||
import materialui.lab.components.treeItem.treeItem
|
||||
import materialui.lab.components.treeView.SingleSelectTreeViewElementBuilder
|
||||
import materialui.lab.components.treeView.treeView
|
||||
import react.FunctionalComponent
|
||||
import react.RBuilder
|
||||
import react.RProps
|
||||
import react.child
|
||||
import react.dom.span
|
||||
|
||||
interface ObjectTreeProps : RProps {
|
||||
var name: Name
|
||||
var selected: Name?
|
||||
var obj: Vision
|
||||
var clickCallback: (Name?) -> Unit
|
||||
}
|
||||
|
||||
private fun RBuilder.treeBranch(name: Name, obj: Vision): Unit {
|
||||
treeItem {
|
||||
val token = name.last()?.toString() ?: "World"
|
||||
attrs {
|
||||
nodeId = name.toString()
|
||||
label {
|
||||
span {
|
||||
+token
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (obj is VisionGroup) {
|
||||
obj.children.entries
|
||||
.filter { !it.key.toString().startsWith("@") } // ignore statics and other hidden children
|
||||
.sortedBy { (it.value as? VisionGroup)?.isEmpty ?: true }
|
||||
.forEach { (childToken, child) ->
|
||||
treeBranch(name + childToken, child)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val ObjectTree: FunctionalComponent<ObjectTreeProps> = component { props ->
|
||||
var selected: String? by state { props.selected.toString() }
|
||||
treeView {
|
||||
this as SingleSelectTreeViewElementBuilder<UL>
|
||||
attrs {
|
||||
this.selected = selected
|
||||
this.onNodeSelect{ _, selectedItem ->
|
||||
selected = selectedItem
|
||||
val itemName = selected?.toName()
|
||||
props.clickCallback(itemName)
|
||||
Unit
|
||||
}
|
||||
defaultCollapseIcon {
|
||||
span{
|
||||
+"-"
|
||||
}
|
||||
//child(ExpandMoreIcon::class) {}
|
||||
}//{<ExpandMoreIcon />}
|
||||
defaultExpandIcon {
|
||||
span{
|
||||
+"+"
|
||||
}
|
||||
//child(ChevronRightIcon::class) {}
|
||||
}//{<ChevronRightIcon />}
|
||||
}
|
||||
treeBranch(props.name, props.obj)
|
||||
}
|
||||
}
|
||||
|
||||
fun RBuilder.objectTree(
|
||||
vision: Vision,
|
||||
selected: Name? = null,
|
||||
clickCallback: (Name?) -> Unit = {}
|
||||
) {
|
||||
child(ObjectTree) {
|
||||
attrs {
|
||||
this.name = Name.EMPTY
|
||||
this.obj = vision
|
||||
this.selected = selected
|
||||
this.clickCallback = clickCallback
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,37 @@
|
||||
package materialui.components.slider
|
||||
|
||||
import kotlinx.html.DIV
|
||||
import kotlinx.html.Tag
|
||||
import materialui.components.MaterialElementBuilder
|
||||
import materialui.components.getValue
|
||||
import materialui.components.inputbase.enums.InputBaseStyle
|
||||
import materialui.components.setValue
|
||||
import react.RClass
|
||||
import react.ReactElement
|
||||
|
||||
class SliderElementBuilder<Props: SliderProps> internal constructor(
|
||||
type: RClass<Props>,
|
||||
classMap: List<Pair<Enum<*>, String>>
|
||||
) : MaterialElementBuilder<DIV, Props>(type, classMap, { DIV(mapOf(), it) }) {
|
||||
fun Tag.classes(vararg classMap: Pair<InputBaseStyle, String>) {
|
||||
classes(classMap.toList())
|
||||
}
|
||||
var Tag.defaultValue: Number? by materialProps
|
||||
var Tag.disabled: Boolean? by materialProps// = false
|
||||
var Tag.getAriaLabel: String? by materialProps
|
||||
var Tag.getAriaValueText: String? by materialProps
|
||||
var Tag.marks: Array<String>? by materialProps
|
||||
var Tag.max: Number? by materialProps// = 100
|
||||
var Tag.min: Number? by materialProps// = 0,
|
||||
var Tag.name: String? by materialProps
|
||||
var Tag.onChange: ((dynamic, Number) -> Unit)? by materialProps
|
||||
var Tag.onChangeCommitted: ((dynamic, Number) -> Unit)? by materialProps
|
||||
var Tag.orientation: SliderOrientation? by materialProps
|
||||
var Tag.scale: ((Number) -> Number)? by materialProps// {it}
|
||||
var Tag.step: Number? by materialProps// = 1,
|
||||
//ThumbComponent = 'span',
|
||||
var Tag.track: SliderTrack by materialProps
|
||||
var Tag.value: Number? by materialProps
|
||||
var Tag.ValueLabelComponent: ReactElement? by materialProps
|
||||
var Tag.valueLabelDisplay: SliderValueLabelDisplay by materialProps
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package materialui.components.slider
|
||||
|
||||
enum class SliderOrientation {
|
||||
horizontal,
|
||||
vertical
|
||||
}
|
||||
|
||||
enum class SliderTrack{
|
||||
normal,
|
||||
`false`,
|
||||
inverted,
|
||||
}
|
||||
|
||||
enum class SliderValueLabelDisplay{
|
||||
on,
|
||||
auto,
|
||||
off
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package materialui.components.slider
|
||||
|
||||
import materialui.components.StandardProps
|
||||
import materialui.components.input.enums.InputStyle
|
||||
import react.RBuilder
|
||||
import react.RClass
|
||||
import react.ReactElement
|
||||
|
||||
@JsModule("@material-ui/core/Slider")
|
||||
private external val sliderModule: dynamic
|
||||
|
||||
external interface SliderProps : StandardProps {
|
||||
var defaultValue: Number?
|
||||
var disabled: Boolean?// = false
|
||||
var getAriaLabel: String?
|
||||
var getAriaValueText: String?
|
||||
var marks: Array<String>?
|
||||
var max: Number?// = 100
|
||||
var min: Number?// = 0,
|
||||
var name: String?
|
||||
var onChange: ((dynamic, Number) -> Unit)?
|
||||
var onChangeCommitted: ((dynamic, Number) -> Unit)?
|
||||
var orientation: SliderOrientation?
|
||||
var scale: ((Number) -> Number)?// {it}
|
||||
var step: Number? // = 1,
|
||||
//ThumbComponent = 'span',
|
||||
var track: SliderTrack
|
||||
var value: Number?
|
||||
var ValueLabelComponent: ReactElement?
|
||||
var valueLabelDisplay: SliderValueLabelDisplay
|
||||
//valueLabelFormat = Identity,
|
||||
}
|
||||
|
||||
@Suppress("UnsafeCastFromDynamic")
|
||||
private val sliderComponent: RClass<SliderProps> = sliderModule.default
|
||||
|
||||
fun RBuilder.slider(vararg classMap: Pair<InputStyle, String>, block: SliderElementBuilder<SliderProps>.() -> Unit) =
|
||||
child(SliderElementBuilder(sliderComponent, classMap.toList()).apply(block).create())
|
22
ui/react/build.gradle.kts
Normal file
@ -0,0 +1,22 @@
|
||||
plugins {
|
||||
id("scientifik.js")
|
||||
}
|
||||
|
||||
kotlin {
|
||||
target {
|
||||
useCommonJs()
|
||||
}
|
||||
}
|
||||
|
||||
val reactVersion by extra("16.13.1")
|
||||
|
||||
dependencies{
|
||||
api(project(":visionforge-core"))
|
||||
|
||||
//api("org.jetbrains:kotlin-react:16.13.1-pre.104-kotlin-1.3.72")
|
||||
api("org.jetbrains:kotlin-react-dom:$reactVersion-pre.104-kotlin-1.3.72")
|
||||
|
||||
api(npm("react", reactVersion))
|
||||
api(npm("react-dom", reactVersion))
|
||||
api(npm("react-is", reactVersion))
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
package hep.dataforge.vision.react
|
||||
|
||||
import kotlinx.css.*
|
||||
import kotlinx.css.properties.deg
|
||||
import kotlinx.css.properties.rotate
|
||||
import styled.StyleSheet
|
||||
|
||||
object TreeStyles : StyleSheet("treeStyles", true) {
|
||||
/**
|
||||
* Remove default bullets
|
||||
*/
|
||||
val tree by css {
|
||||
paddingLeft = 8.px
|
||||
marginLeft = 0.px
|
||||
listStyleType = ListStyleType.none
|
||||
}
|
||||
|
||||
/**
|
||||
* Style the caret/arrow
|
||||
*/
|
||||
val treeCaret by css {
|
||||
cursor = Cursor.pointer
|
||||
userSelect = UserSelect.none
|
||||
/* Create the caret/arrow with a unicode, and style it */
|
||||
before {
|
||||
content = "\u25B6".quoted
|
||||
color = Color.black
|
||||
display = Display.inlineBlock
|
||||
marginRight = 6.px
|
||||
}
|
||||
}
|
||||
|
||||
val treeItem by css {
|
||||
alignItems = Align.center
|
||||
paddingLeft = 10.px
|
||||
borderLeftStyle = BorderStyle.dashed
|
||||
borderLeftWidth = 1.px
|
||||
borderLeftColor = Color.lightGray
|
||||
}
|
||||
|
||||
val treeLeaf by css {
|
||||
display = Display.flex
|
||||
flexDirection = FlexDirection.row
|
||||
userSelect = UserSelect.none
|
||||
alignItems = Align.center
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Rotate the caret/arrow icon when clicked on (using JavaScript)
|
||||
*/
|
||||
val treeCaredDown by css {
|
||||
before {
|
||||
content = "\u25B6".quoted
|
||||
color = Color.black
|
||||
display = Display.inlineBlock
|
||||
marginRight = 6.px
|
||||
transform.rotate(90.deg)
|
||||
}
|
||||
}
|
||||
|
||||
val treeLabel by css {
|
||||
overflow = Overflow.hidden
|
||||
}
|
||||
|
||||
val treeLabelInactive by css {
|
||||
color = Color.lightGray
|
||||
}
|
||||
|
||||
val treeLabelSelected by css {
|
||||
backgroundColor = Color.lightBlue
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,265 @@
|
||||
package hep.dataforge.vision.react
|
||||
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.meta.descriptors.*
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.NameToken
|
||||
import hep.dataforge.names.plus
|
||||
import hep.dataforge.values.Value
|
||||
import kotlinx.css.*
|
||||
import kotlinx.css.properties.TextDecoration
|
||||
import kotlinx.html.js.onClickFunction
|
||||
import org.w3c.dom.Element
|
||||
import org.w3c.dom.events.Event
|
||||
import react.*
|
||||
import react.dom.div
|
||||
import react.dom.render
|
||||
import styled.*
|
||||
|
||||
interface ConfigEditorItemProps : RProps {
|
||||
|
||||
/**
|
||||
* Root config object - always non null
|
||||
*/
|
||||
var root: Config
|
||||
|
||||
/**
|
||||
* Full path to the displayed node in [root]. Could be empty
|
||||
*/
|
||||
var name: Name
|
||||
|
||||
/**
|
||||
* Root default
|
||||
*/
|
||||
var default: Meta?
|
||||
|
||||
/**
|
||||
* Root descriptor
|
||||
*/
|
||||
var descriptor: NodeDescriptor?
|
||||
}
|
||||
|
||||
private val ConfigEditorItem: FunctionalComponent<ConfigEditorItemProps> = component { props ->
|
||||
configEditorItem(props)
|
||||
}
|
||||
|
||||
private fun RFBuilder.configEditorItem(props: ConfigEditorItemProps) {
|
||||
var expanded: Boolean by state { true }
|
||||
var item: MetaItem<Config>? by state { props.root[props.name] }
|
||||
val descriptorItem: ItemDescriptor? = props.descriptor?.get(props.name)
|
||||
val defaultItem = props.default?.get(props.name)
|
||||
var actualItem: MetaItem<Meta>? by state { item ?: defaultItem ?: descriptorItem?.defaultItem() }
|
||||
|
||||
val token = props.name.last()?.toString() ?: "Properties"
|
||||
|
||||
fun update() {
|
||||
item = props.root[props.name]
|
||||
actualItem = item ?: defaultItem ?: descriptorItem?.defaultItem()
|
||||
}
|
||||
|
||||
useEffectWithCleanup(listOf(props.root)) {
|
||||
props.root.onChange(this) { name, _, _ ->
|
||||
if (name == props.name) {
|
||||
update()
|
||||
}
|
||||
}
|
||||
return@useEffectWithCleanup { props.root.removeListener(this) }
|
||||
}
|
||||
|
||||
val expanderClick: (Event) -> Unit = {
|
||||
expanded = !expanded
|
||||
}
|
||||
|
||||
val valueChanged: (Value?) -> Unit = {
|
||||
if (it == null) {
|
||||
props.root.remove(props.name)
|
||||
} else {
|
||||
props.root[props.name] = it
|
||||
}
|
||||
update()
|
||||
}
|
||||
|
||||
val removeClick: (Event) -> Unit = {
|
||||
props.root.remove(props.name)
|
||||
update()
|
||||
}
|
||||
|
||||
when (actualItem) {
|
||||
is MetaItem.NodeItem -> {
|
||||
div {
|
||||
styledSpan {
|
||||
css {
|
||||
+TreeStyles.treeCaret
|
||||
if (expanded) {
|
||||
+TreeStyles.treeCaredDown
|
||||
}
|
||||
}
|
||||
attrs {
|
||||
onClickFunction = expanderClick
|
||||
}
|
||||
}
|
||||
styledSpan {
|
||||
css {
|
||||
+TreeStyles.treeLabel
|
||||
if (item == null) {
|
||||
+TreeStyles.treeLabelInactive
|
||||
}
|
||||
}
|
||||
+token
|
||||
}
|
||||
}
|
||||
if (expanded) {
|
||||
styledUl {
|
||||
css {
|
||||
+TreeStyles.tree
|
||||
}
|
||||
val keys = buildSet<NameToken> {
|
||||
(descriptorItem as? NodeDescriptor)?.items?.keys?.forEach {
|
||||
add(NameToken(it))
|
||||
}
|
||||
item?.node?.items?.keys?.let { addAll(it) }
|
||||
defaultItem?.node?.items?.keys?.let { addAll(it) }
|
||||
}
|
||||
|
||||
keys.forEach { token ->
|
||||
styledLi {
|
||||
css {
|
||||
+TreeStyles.treeItem
|
||||
}
|
||||
child(ConfigEditorItem) {
|
||||
attrs {
|
||||
this.key = props.name.toString()
|
||||
this.root = props.root
|
||||
this.name = props.name + token
|
||||
this.default = props.default
|
||||
this.descriptor = props.descriptor
|
||||
}
|
||||
}
|
||||
//configEditor(props.root, props.name + token, props.descriptor, props.default)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is MetaItem.ValueItem -> {
|
||||
styledDiv {
|
||||
css {
|
||||
+TreeStyles.treeLeaf
|
||||
justifyContent = JustifyContent.flexEnd
|
||||
}
|
||||
styledDiv {
|
||||
css {
|
||||
flexGrow = 1.0
|
||||
}
|
||||
styledSpan {
|
||||
css {
|
||||
+TreeStyles.treeLabel
|
||||
if (item == null) {
|
||||
+TreeStyles.treeLabelInactive
|
||||
}
|
||||
}
|
||||
+token
|
||||
}
|
||||
}
|
||||
styledDiv {
|
||||
valueChooser(
|
||||
props.name,
|
||||
actualItem,
|
||||
descriptorItem as? ValueDescriptor,
|
||||
valueChanged
|
||||
)
|
||||
}
|
||||
styledDiv {
|
||||
css {
|
||||
flexShrink = 1.0
|
||||
}
|
||||
styledButton {
|
||||
css {
|
||||
backgroundColor = Color.white
|
||||
borderStyle = BorderStyle.solid
|
||||
borderRadius = 2.px
|
||||
padding(1.px, 5.px)
|
||||
marginLeft = 4.px
|
||||
textAlign = TextAlign.center
|
||||
textDecoration = TextDecoration.none
|
||||
display = Display.inlineBlock
|
||||
cursor = Cursor.pointer
|
||||
disabled {
|
||||
cursor = Cursor.auto
|
||||
borderStyle = BorderStyle.dashed
|
||||
color = Color.lightGray
|
||||
}
|
||||
}
|
||||
+"\u00D7"
|
||||
attrs {
|
||||
if (item == null) {
|
||||
disabled = true
|
||||
} else {
|
||||
onClickFunction = removeClick
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface ConfigEditorProps : RProps {
|
||||
var id: Name
|
||||
var root: Config
|
||||
var default: Meta?
|
||||
var descriptor: NodeDescriptor?
|
||||
}
|
||||
|
||||
val ConfigEditor = component<ConfigEditorProps> { props ->
|
||||
child(ConfigEditorItem) {
|
||||
attrs {
|
||||
this.key = ""
|
||||
this.root = props.root
|
||||
this.name = Name.EMPTY
|
||||
this.default = props.default
|
||||
this.descriptor = props.descriptor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Element.configEditor(config: Config, descriptor: NodeDescriptor? = null, default: Meta? = null, key: Any? = null) {
|
||||
render(this) {
|
||||
child(ConfigEditor) {
|
||||
attrs {
|
||||
this.key = key?.toString() ?: ""
|
||||
this.root = config
|
||||
this.descriptor = descriptor
|
||||
this.default = default
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun RBuilder.configEditor(config: Config, descriptor: NodeDescriptor? = null, default: Meta? = null, key: Any? = null) {
|
||||
child(ConfigEditor) {
|
||||
attrs {
|
||||
this.key = key?.toString() ?: ""
|
||||
this.root = config
|
||||
this.descriptor = descriptor
|
||||
this.default = default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun RBuilder.configEditor(
|
||||
obj: Configurable,
|
||||
descriptor: NodeDescriptor? = obj.descriptor,
|
||||
default: Meta? = null,
|
||||
key: Any? = null
|
||||
) {
|
||||
child(ConfigEditor) {
|
||||
attrs {
|
||||
this.key = key?.toString() ?: ""
|
||||
this.root = obj.config
|
||||
this.descriptor = descriptor
|
||||
this.default = default
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package hep.dataforge.vision.react
|
||||
|
||||
import kotlinx.css.Display
|
||||
import kotlinx.css.FlexDirection
|
||||
import kotlinx.css.display
|
||||
import kotlinx.css.flexDirection
|
||||
import kotlinx.html.DIV
|
||||
import react.RBuilder
|
||||
import styled.StyledDOMBuilder
|
||||
import styled.css
|
||||
import styled.styledDiv
|
||||
|
||||
inline fun RBuilder.flexColumn(block: StyledDOMBuilder<DIV>.() -> Unit) =
|
||||
styledDiv {
|
||||
css {
|
||||
display = Display.flex
|
||||
flexDirection = FlexDirection.column
|
||||
}
|
||||
block()
|
||||
}
|
||||
|
||||
inline fun RBuilder.flexRow(block: StyledDOMBuilder<DIV>.() -> Unit) =
|
||||
styledDiv {
|
||||
css {
|
||||
display = Display.flex
|
||||
flexDirection = FlexDirection.row
|
||||
}
|
||||
block()
|
||||
}
|
38
ui/react/src/main/kotlin/hep/dataforge/vision/react/react.kt
Normal file
@ -0,0 +1,38 @@
|
||||
package hep.dataforge.vision.react
|
||||
|
||||
import react.*
|
||||
import kotlin.properties.ReadWriteProperty
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
class RFBuilder : RBuilder()
|
||||
|
||||
/**
|
||||
* Get functional component from [func]
|
||||
*/
|
||||
fun <P : RProps> component(
|
||||
func: RFBuilder.(props: P) -> Unit
|
||||
): FunctionalComponent<P> {
|
||||
return { props: P ->
|
||||
val nodes = RFBuilder().apply { func(props) }.childList
|
||||
when (nodes.size) {
|
||||
0 -> null
|
||||
1 -> nodes.first()
|
||||
else -> createElement(Fragment, kotlinext.js.js {}, *nodes.toTypedArray())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> RFBuilder.state(init: () -> T): ReadWriteProperty<Any?, T> =
|
||||
object : ReadWriteProperty<Any?, T> {
|
||||
val pair = react.useState(init)
|
||||
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
|
||||
return pair.first
|
||||
}
|
||||
|
||||
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
|
||||
pair.second(value)
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> RFBuilder.memoize(vararg deps: dynamic, builder: () -> T): T = useMemo(builder, deps)
|
||||
|
@ -0,0 +1,9 @@
|
||||
package hep.dataforge.vision.react
|
||||
|
||||
import styled.StyleSheet
|
||||
|
||||
|
||||
class MainStyle: StyleSheet("main", true){
|
||||
|
||||
|
||||
}
|