Readme update #25 #26
120
README.md
@ -1,41 +1,99 @@
|
||||
[![JetBrains Research](https://jb.gg/badges/research.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub)
|
||||
|
||||
# DataForge Visualisation Platform
|
||||
# DataForge Visualization Platform
|
||||
|
||||
This repository contains [DataForge](http://npm.mipt.ru/dataforge/)
|
||||
(also [here](https://github.com/mipt-npm/dataforge-core)) components useful for visualization in
|
||||
various scientific applications. The main application for now is 3D visualization for particle
|
||||
physics experiments. Other applications including 2D plots are planned for future.
|
||||
## Table of contents
|
||||
|
||||
The project is developed as a Kotlin multiplatform application, currently
|
||||
targeting browser JavaScript and JVM.
|
||||
* [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)
|
||||
* [Demonstrations](#demonstrations)
|
||||
* [Spatial Showcase](#spatial-showcase)
|
||||
* [Muon Monitor](#muon-monitor-visualization)
|
||||
* [GDML Example](#gdml-example)
|
||||
|
||||
Main features:
|
||||
|
||||
## Introduction
|
||||
|
||||
This repository contains a [DataForge](#about-dataforge)\-based framework
|
||||
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.
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
The main framework's features for now include:
|
||||
- 3D visualization of complex experimental set-ups
|
||||
- Event display such as particle tracks, etc.
|
||||
- Scales up to few hundred thousands of elements
|
||||
- Camera move, rotate, zoom-in and zoom-out
|
||||
- Object tree with property editor
|
||||
- Scene graph as an object tree with property editor
|
||||
- Settings export and import
|
||||
- Multiple platform support
|
||||
|
||||
|
||||
## Modules contained in this repository:
|
||||
## About DataForge
|
||||
|
||||
DataForge is a software framework for automated scientific data processing. DataForge Visualization
|
||||
Platform uses some of the concepts and modules of DataForge, including: `Meta`, `Configuration`, `Context`,
|
||||
`Provider`, and some others.
|
||||
|
||||
To learn more about DataForge, please consult the following URLs:
|
||||
* [Kotlin multiplatform implementation of DataForge](https://github.com/mipt-npm/dataforge-core)
|
||||
* [DataForge documentation](http://npm.mipt.ru/dataforge/)
|
||||
* [Original implementation of DataForge](https://bitbucket.org/Altavir/dataforge/src/default/)
|
||||
|
||||
|
||||
## Modules contained in this repository
|
||||
|
||||
### dataforge-vis-common
|
||||
|
||||
Common visualisation objects such as VisualObject and VisualGroup.
|
||||
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`).
|
||||
|
||||
##### Class diagram:
|
||||
|
||||
![](doc/resources/class-diag-common.png)
|
||||
|
||||
|
||||
### dataforge-vis-spatial
|
||||
|
||||
Includes common description and serializers for 3D visualisation, JavaFX and Three.js implementations.
|
||||
Includes common classes and serializers for 3D visualization, Three.js and JavaFX implementations.
|
||||
|
||||
##### Class diagram:
|
||||
|
||||
![](doc/resources/class-diag-3d.png)
|
||||
|
||||
##### Prototypes
|
||||
|
||||
One of the important features of the framework is support for 3D object prototypes (sometimes
|
||||
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.
|
||||
|
||||
##### 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`.
|
||||
|
||||
|
||||
### dataforge-vis-spatial-gdml
|
||||
|
||||
GDML bindings for 3D visualisation (to be moved to gdml project).
|
||||
GDML bindings for 3D visualization (to be moved to gdml project).
|
||||
|
||||
|
||||
### dataforge-vis-jsroot
|
||||
@ -45,20 +103,23 @@ Some JSROOT bindings.
|
||||
Note: Currently, this part is experimental and put here for completeness. This module may not build.
|
||||
|
||||
|
||||
### demo
|
||||
## Demonstrations
|
||||
|
||||
Several demonstrations of using the dataforge-vis framework:
|
||||
The `demo` module contains several demonstrations of using the `dataforge-vis` framework:
|
||||
|
||||
##### spatial-showcase
|
||||
### Spatial Showcase
|
||||
|
||||
Contains a simple demonstration (grid with a few shapes that you can rotate, move camera, etc.).
|
||||
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/distribution/installJsDist` Gradle task, then open
|
||||
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.
|
||||
|
||||
Other demos can be built similarly.
|
||||
##### Example view:
|
||||
|
||||
##### muon-monitor
|
||||
![](doc/resources/spatial-showcase.png)
|
||||
|
||||
### Muon Monitor Visualization
|
||||
|
||||
A full-stack application example, showing the
|
||||
[Muon Monitor](http://npm.mipt.ru/projects/physics.html#mounMonitor) experiment set-up.
|
||||
@ -68,8 +129,19 @@ Includes server back-end generating events, as well as visualization front-end.
|
||||
To run full-stack app (both server and browser front-end), run
|
||||
`demo/muon-monitor/application/run` task.
|
||||
|
||||
##### gdml
|
||||
##### Example view:
|
||||
|
||||
Visualization example for geometry defined as GDML file. Once you open Web application,
|
||||
drag-and-drop GDML file to the window to see visualization. For example file, use
|
||||
`demo\gdml\src\jsMain\resources\cubes.gdml`.
|
||||
![](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`.
|
||||
|
||||
##### Example view:
|
||||
|
||||
![](doc/resources/gdml-demo.png)
|
||||
|
@ -26,7 +26,7 @@ allprojects {
|
||||
}
|
||||
|
||||
group = "hep.dataforge"
|
||||
version = "0.1.0-dev"
|
||||
version = "0.1.1-dev"
|
||||
}
|
||||
|
||||
subprojects{
|
||||
|
@ -37,7 +37,7 @@ kotlin {
|
||||
jsMain{
|
||||
dependencies {
|
||||
api("hep.dataforge:dataforge-output-html:$dataforgeVersion")
|
||||
api(npm("bootstrap","4.4.1"))
|
||||
//api(npm("bootstrap","4.4.1"))
|
||||
implementation(npm("jsoneditor"))
|
||||
implementation(npm("file-saver"))
|
||||
}
|
||||
|
@ -1,7 +1,10 @@
|
||||
package hep.dataforge.vis.common
|
||||
|
||||
import hep.dataforge.meta.MetaItem
|
||||
import hep.dataforge.names.*
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.NameToken
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.names.isEmpty
|
||||
import kotlinx.serialization.Transient
|
||||
|
||||
|
||||
@ -76,7 +79,7 @@ abstract class AbstractVisualGroup : AbstractVisualObject(), MutableVisualGroup
|
||||
* Add named or unnamed child to the group. If key is null the child is considered unnamed. Both key and value are not
|
||||
* allowed to be null in the same time. If name is present and [child] is null, the appropriate element is removed.
|
||||
*/
|
||||
override fun set(name: Name, child: VisualObject?) {
|
||||
override fun set(name: Name, child: VisualObject?): Unit {
|
||||
when {
|
||||
name.isEmpty() -> {
|
||||
if (child != null) {
|
||||
@ -100,13 +103,4 @@ abstract class AbstractVisualGroup : AbstractVisualObject(), MutableVisualGroup
|
||||
structureChangeListeners.forEach { it.callback(name, child) }
|
||||
}
|
||||
|
||||
operator fun set(key: String, child: VisualObject?): Unit {
|
||||
if (key.isBlank()) {
|
||||
if(child!= null) {
|
||||
addStatic(child)
|
||||
}
|
||||
} else {
|
||||
set(key.toName(), child)
|
||||
}
|
||||
}
|
||||
}
|
@ -73,6 +73,9 @@ abstract class AbstractVisualObject : VisualObject {
|
||||
styleCache = it
|
||||
}
|
||||
|
||||
/**
|
||||
* All available properties in a layered form
|
||||
*/
|
||||
override fun allProperties(): Laminate = Laminate(properties, mergedStyles)
|
||||
|
||||
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? {
|
||||
|
@ -6,10 +6,11 @@ import hep.dataforge.io.serialization.MetaSerializer
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.asName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import kotlinx.serialization.*
|
||||
|
||||
/**
|
||||
* A container for styles
|
||||
*/
|
||||
@Serializable
|
||||
class StyleSheet() {
|
||||
@Transient
|
||||
@ -28,7 +29,7 @@ class StyleSheet() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a style without notifying
|
||||
* Define a style without notifying owner
|
||||
*/
|
||||
fun define(key: String, style: Meta?) {
|
||||
if (style == null) {
|
||||
@ -48,6 +49,20 @@ class StyleSheet() {
|
||||
val newStyle = get(key)?.let { buildMeta(it, builder) } ?: buildMeta(builder)
|
||||
set(key, newStyle.seal())
|
||||
}
|
||||
|
||||
companion object: KSerializer<StyleSheet>{
|
||||
override val descriptor: SerialDescriptor
|
||||
get() = TODO("Not yet implemented")
|
||||
|
||||
override fun deserialize(decoder: Decoder): StyleSheet {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun serialize(encoder: Encoder, obj: StyleSheet) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private fun VisualObject.styleChanged(key: String, oldStyle: Meta?, newStyle: Meta?) {
|
||||
|
@ -13,7 +13,7 @@ interface VisualFactory<T : VisualObject> {
|
||||
): T
|
||||
}
|
||||
|
||||
class VisualPlugin(meta: Meta) : AbstractPlugin(meta) {
|
||||
class Visual(meta: Meta) : AbstractPlugin(meta) {
|
||||
override val tag: PluginTag get() = Companion.tag
|
||||
|
||||
/**
|
||||
@ -27,11 +27,11 @@ class VisualPlugin(meta: Meta) : AbstractPlugin(meta) {
|
||||
return visualFactories[T::class]?.invoke(context, parent, meta) as T?
|
||||
}
|
||||
|
||||
companion object : PluginFactory<VisualPlugin> {
|
||||
companion object : PluginFactory<Visual> {
|
||||
override val tag: PluginTag = PluginTag(name = "visual", group = PluginTag.DATAFORGE_GROUP)
|
||||
override val type: KClass<out VisualPlugin> = VisualPlugin::class
|
||||
override val type: KClass<out Visual> = Visual::class
|
||||
|
||||
override fun invoke(meta: Meta, context: Context): VisualPlugin = VisualPlugin(meta)
|
||||
override fun invoke(meta: Meta, context: Context): Visual = Visual(meta)
|
||||
|
||||
const val VISUAL_FACTORY_TYPE = "visual.factory"
|
||||
}
|
@ -16,6 +16,10 @@ interface VisualGroup : Provider, Iterable<VisualObject>, VisualObject {
|
||||
|
||||
val styleSheet: StyleSheet?
|
||||
|
||||
/**
|
||||
* A map of direct children for specific target
|
||||
* (currently "visual" or "style")
|
||||
*/
|
||||
override fun provideTop(target: String): Map<Name, Any> =
|
||||
when (target) {
|
||||
VisualObject.TYPE -> children.flatMap { (key, value) ->
|
||||
@ -49,7 +53,7 @@ interface VisualGroup : Provider, Iterable<VisualObject>, VisualObject {
|
||||
*/
|
||||
fun attachChildren() {
|
||||
styleSheet?.owner = this
|
||||
this.children.values.forEach {
|
||||
children.values.forEach {
|
||||
it.parent = this
|
||||
(it as? VisualGroup)?.attachChildren()
|
||||
}
|
||||
@ -84,6 +88,10 @@ interface MutableVisualGroup : VisualGroup {
|
||||
operator fun set(name: Name, child: VisualObject?)
|
||||
}
|
||||
|
||||
operator fun VisualGroup.get(str: String?) = get(str?.toName() ?: Name.EMPTY)
|
||||
operator fun VisualGroup.get(str: String?): VisualObject? = get(str?.toName() ?: Name.EMPTY)
|
||||
|
||||
operator fun MutableVisualGroup.set(key: String, child: VisualObject?) {
|
||||
set(key.toName(), child)
|
||||
}
|
||||
|
||||
fun MutableVisualGroup.removeAll() = children.keys.map { it.asName() }.forEach { this[it] = null }
|
@ -2,9 +2,7 @@ package hep.dataforge.vis.common
|
||||
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.NameToken
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.names.toName
|
||||
import hep.dataforge.values.Value
|
||||
import kotlin.jvm.JvmName
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
@ -48,7 +46,7 @@ class VisualObjectDelegateWrapper<T>(
|
||||
|
||||
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
|
||||
val name = key ?: property.name.asName()
|
||||
return read(obj.getProperty(name,inherited))?:default
|
||||
return read(obj.getProperty(name, inherited)) ?: default
|
||||
}
|
||||
|
||||
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
|
||||
@ -58,75 +56,72 @@ class VisualObjectDelegateWrapper<T>(
|
||||
}
|
||||
|
||||
|
||||
fun VisualObject.value(default: Value? = null, key: String? = null, inherited: Boolean = false) =
|
||||
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.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, key: String? = null, inherited: Boolean = false) =
|
||||
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.string }
|
||||
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, key: String? = null, inherited: Boolean = false) =
|
||||
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.boolean }
|
||||
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, key: String? = null, inherited: Boolean = false) =
|
||||
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.number }
|
||||
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, key: String? = null, inherited: Boolean = false) =
|
||||
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.double }
|
||||
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, key: String? = null, inherited: Boolean = false) =
|
||||
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.int }
|
||||
fun VisualObject.int(default: Int? = null, name: Name? = null, inherited: Boolean = false) =
|
||||
VisualObjectDelegateWrapper(this, name, default, inherited) { it.int }
|
||||
|
||||
|
||||
fun VisualObject.node(key: String? = null, inherited: Boolean = true) =
|
||||
VisualObjectDelegateWrapper(this, key?.toName(), null, inherited) { it.node }
|
||||
fun VisualObject.node(name: Name? = null, inherited: Boolean = true) =
|
||||
VisualObjectDelegateWrapper(this, name, null, inherited) { it.node }
|
||||
|
||||
fun VisualObject.item(key: String? = null, inherited: Boolean = true) =
|
||||
VisualObjectDelegateWrapper(this, key?.toName(), null, inherited) { it }
|
||||
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, key: String? = null, inherited: Boolean = false) =
|
||||
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.string }
|
||||
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, key: String? = null, inherited: Boolean = false) =
|
||||
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.boolean }
|
||||
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, key: String? = null, inherited: Boolean = false) =
|
||||
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.number }
|
||||
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, key: String? = null, inherited: Boolean = false) =
|
||||
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.double }
|
||||
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, key: String? = null, inherited: Boolean = false) =
|
||||
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.int }
|
||||
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, key: String? = null, inherited: Boolean = false) =
|
||||
VisualObjectDelegateWrapper(
|
||||
this,
|
||||
key?.let { NameToken(it).asName() },
|
||||
default,
|
||||
inherited
|
||||
) { item -> item.string?.let { enumValueOf<E>(it) } }
|
||||
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(
|
||||
key: String? = null,
|
||||
name: Name? = null,
|
||||
transformer: (Sequence<MetaItem<*>>) -> T
|
||||
): ReadOnlyProperty<VisualObject, T> {
|
||||
return object : ReadOnlyProperty<Any?, T> {
|
||||
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
|
||||
val name = key?.toName() ?: property.name.asName()
|
||||
val actualName = name ?: property.name.asName()
|
||||
val sequence = sequence<MetaItem<*>> {
|
||||
var thisObj: VisualObject? = this@merge
|
||||
while (thisObj != null) {
|
||||
thisObj.config[name]?.let { yield(it) }
|
||||
thisObj.config[actualName]?.let { yield(it) }
|
||||
thisObj = thisObj.parent
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,7 @@
|
||||
package hep.dataforge.vis.js.editor
|
||||
|
||||
import kotlinx.html.TagConsumer
|
||||
import kotlinx.html.*
|
||||
import kotlinx.html.js.div
|
||||
import kotlinx.html.js.h3
|
||||
import org.w3c.dom.HTMLElement
|
||||
|
||||
inline fun TagConsumer<HTMLElement>.card(title: String, crossinline block: TagConsumer<HTMLElement>.() -> Unit) {
|
||||
@ -13,3 +12,50 @@ inline fun TagConsumer<HTMLElement>.card(title: String, crossinline block: TagCo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
@ -26,12 +26,11 @@ fun Element.displayObjectTree(
|
||||
}
|
||||
|
||||
private fun TagConsumer<HTMLElement>.subTree(
|
||||
fullName: Name,
|
||||
name: Name,
|
||||
obj: VisualObject,
|
||||
clickCallback: (Name) -> Unit
|
||||
) {
|
||||
// val fullName = parentName + token
|
||||
val token = fullName.last()?.toString()?:"World"
|
||||
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("@") }) {
|
||||
@ -40,7 +39,7 @@ private fun TagConsumer<HTMLElement>.subTree(
|
||||
toggle = span("objTree-caret")
|
||||
label("objTree-label") {
|
||||
+token
|
||||
onClickFunction = { clickCallback(fullName) }
|
||||
onClickFunction = { clickCallback(name) }
|
||||
}
|
||||
}
|
||||
val subtree = ul("objTree-subtree")
|
||||
@ -55,7 +54,7 @@ private fun TagConsumer<HTMLElement>.subTree(
|
||||
.forEach { (childToken, child) ->
|
||||
append {
|
||||
li().apply {
|
||||
subTree(fullName + childToken, child, clickCallback)
|
||||
subTree(name + childToken, child, clickCallback)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -70,7 +69,7 @@ private fun TagConsumer<HTMLElement>.subTree(
|
||||
span("objTree-leaf")
|
||||
label("objTree-label") {
|
||||
+token
|
||||
onClickFunction = { clickCallback(fullName) }
|
||||
onClickFunction = { clickCallback(name) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ kotlin {
|
||||
val commonMain by getting {
|
||||
dependencies {
|
||||
api(project(":dataforge-vis-spatial"))
|
||||
api("scientifik:gdml:0.1.4")
|
||||
api("scientifik:gdml:0.1.6")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.names.plus
|
||||
import hep.dataforge.vis.common.get
|
||||
import hep.dataforge.vis.common.set
|
||||
import hep.dataforge.vis.spatial.*
|
||||
import hep.dataforge.vis.spatial.World.ONE
|
||||
import hep.dataforge.vis.spatial.World.ZERO
|
||||
@ -66,6 +67,12 @@ private fun VisualGroup3D.addSolid(
|
||||
solid.deltaphi * aScale,
|
||||
name
|
||||
)
|
||||
is GDMLCone -> cone(solid.rmax1, solid.z, solid.rmax2, name = name) {
|
||||
require(solid.rmin1 == 0.0) { "Empty cones are not supported" }
|
||||
require(solid.rmin2 == 0.0) { "Empty cones are not supported" }
|
||||
startAngle = solid.startphi.toFloat()
|
||||
angle = solid.deltaphi.toFloat()
|
||||
}
|
||||
is GDMLXtru -> extrude(name) {
|
||||
shape {
|
||||
solid.vertices.forEach {
|
||||
@ -171,7 +178,7 @@ private fun VisualGroup3D.addPhysicalVolume(
|
||||
context.proto[fullName] = volume(context, volume)
|
||||
}
|
||||
|
||||
this[physVolume.name ?: ""] = Proxy(fullName).apply {
|
||||
this[physVolume.name ?: ""] = Proxy(this, fullName).apply {
|
||||
withPosition(
|
||||
context.lUnit,
|
||||
physVolume.resolvePosition(context.root),
|
||||
|
@ -135,7 +135,7 @@ private fun jsonSchema(descriptor: SerialDescriptor, context: SerialModule): Jso
|
||||
}
|
||||
|
||||
fun main() {
|
||||
val context = Visual3DPlugin.serialModule
|
||||
val context = Visual3D.serialModule
|
||||
val definitions = json {
|
||||
"children" to json {
|
||||
"anyOf" to jsonArray {
|
||||
|
@ -8,7 +8,7 @@ import java.nio.file.Path
|
||||
|
||||
fun GDML.Companion.readFile(file: Path): GDML {
|
||||
val xmlReader = StAXReader(Files.newInputStream(file), "UTF-8")
|
||||
return GDML.format.parse(GDML.serializer(), xmlReader)
|
||||
return format.parse(GDML.serializer(), xmlReader)
|
||||
}
|
||||
|
||||
fun VisualGroup3D.gdml(file: Path, key: String = "", transformer: GDMLTransformer.() -> Unit = {}) {
|
||||
|
@ -20,7 +20,7 @@ kotlin {
|
||||
}
|
||||
jvmMain {
|
||||
dependencies {
|
||||
implementation("org.fxyz3d:fxyz3d:0.5.2") {
|
||||
api("org.fxyz3d:fxyz3d:0.5.2") {
|
||||
exclude(module = "slf4j-simple")
|
||||
}
|
||||
api("org.jetbrains.kotlinx:kotlinx-coroutines-javafx:${Scientifik.coroutinesVersion}")
|
||||
|
@ -10,13 +10,15 @@ import hep.dataforge.meta.get
|
||||
import hep.dataforge.vis.common.AbstractVisualObject
|
||||
import hep.dataforge.vis.common.VisualFactory
|
||||
import hep.dataforge.vis.common.VisualObject
|
||||
import hep.dataforge.vis.common.set
|
||||
import hep.dataforge.vis.spatial.Box.Companion.TYPE_NAME
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
@Serializable
|
||||
@SerialName("3d.box")
|
||||
@SerialName(TYPE_NAME)
|
||||
class Box(
|
||||
val xSize: Float,
|
||||
val ySize: Float,
|
||||
@ -52,7 +54,8 @@ class Box(
|
||||
}
|
||||
|
||||
companion object : VisualFactory<Box> {
|
||||
const val TYPE = "geometry.3d.box"
|
||||
|
||||
const val TYPE_NAME = "3d.box"
|
||||
|
||||
override val type: KClass<Box> get() = Box::class
|
||||
|
||||
|
@ -6,6 +6,8 @@ import hep.dataforge.io.serialization.ConfigSerializer
|
||||
import hep.dataforge.meta.Config
|
||||
import hep.dataforge.meta.update
|
||||
import hep.dataforge.vis.common.AbstractVisualObject
|
||||
import hep.dataforge.vis.common.set
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
|
||||
@ -16,6 +18,7 @@ enum class CompositeType {
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@SerialName("3d.composite")
|
||||
class Composite(
|
||||
val compositeType: CompositeType,
|
||||
val first: VisualObject3D,
|
||||
|
@ -5,6 +5,8 @@ package hep.dataforge.vis.spatial
|
||||
import hep.dataforge.io.serialization.ConfigSerializer
|
||||
import hep.dataforge.meta.Config
|
||||
import hep.dataforge.vis.common.AbstractVisualObject
|
||||
import hep.dataforge.vis.common.set
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import kotlin.math.cos
|
||||
@ -14,6 +16,7 @@ import kotlin.math.sin
|
||||
* A cylinder or cut cone segment
|
||||
*/
|
||||
@Serializable
|
||||
@SerialName("3d.cone")
|
||||
class ConeSegment(
|
||||
var radius: Float,
|
||||
var height: Float,
|
||||
@ -83,3 +86,16 @@ inline fun VisualGroup3D.cylinder(
|
||||
height.toFloat(),
|
||||
r.toFloat()
|
||||
).apply(block).also { set(name, it) }
|
||||
|
||||
|
||||
inline fun VisualGroup3D.cone(
|
||||
bottomRadius: Number,
|
||||
height: Number,
|
||||
upperRadius: Number = 0.0,
|
||||
name: String = "",
|
||||
block: ConeSegment.() -> Unit = {}
|
||||
): ConeSegment = ConeSegment(
|
||||
bottomRadius.toFloat(),
|
||||
height.toFloat(),
|
||||
upperRadius = upperRadius.toFloat()
|
||||
).apply(block).also { set(name, it) }
|
@ -5,10 +5,13 @@ package hep.dataforge.vis.spatial
|
||||
import hep.dataforge.io.serialization.ConfigSerializer
|
||||
import hep.dataforge.meta.Config
|
||||
import hep.dataforge.vis.common.AbstractVisualObject
|
||||
import hep.dataforge.vis.common.set
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
|
||||
@Serializable
|
||||
@SerialName("3d.convex")
|
||||
class Convex(val points: List<Point3D>) : AbstractVisualObject(), VisualObject3D {
|
||||
|
||||
@Serializable(ConfigSerializer::class)
|
||||
|
@ -4,6 +4,8 @@ package hep.dataforge.vis.spatial
|
||||
import hep.dataforge.io.serialization.ConfigSerializer
|
||||
import hep.dataforge.meta.Config
|
||||
import hep.dataforge.vis.common.AbstractVisualObject
|
||||
import hep.dataforge.vis.common.set
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import kotlin.math.PI
|
||||
@ -37,6 +39,7 @@ fun Shape2DBuilder.polygon(vertices: Int, radius: Number) {
|
||||
data class Layer(var x: Float, var y: Float, var z: Float, var scale: Float)
|
||||
|
||||
@Serializable
|
||||
@SerialName("3d.extrude")
|
||||
class Extruded(
|
||||
var shape: List<Point2D> = ArrayList(),
|
||||
var layers: MutableList<Layer> = ArrayList()
|
||||
|
@ -31,6 +31,9 @@ fun GeometryBuilder<*>.face4(
|
||||
face(vertex1, vertex3, vertex4, normal, meta)
|
||||
}
|
||||
|
||||
/**
|
||||
* [Shape] is a [VisualObject3D] that can represent its own geometry as a set of polygons.
|
||||
*/
|
||||
interface Shape : VisualObject3D {
|
||||
fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>)
|
||||
}
|
||||
|
@ -5,10 +5,13 @@ package hep.dataforge.vis.spatial
|
||||
import hep.dataforge.io.serialization.ConfigSerializer
|
||||
import hep.dataforge.meta.Config
|
||||
import hep.dataforge.vis.common.AbstractVisualObject
|
||||
import hep.dataforge.vis.common.set
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
|
||||
@Serializable
|
||||
@SerialName("3d.label")
|
||||
class Label3D(var text: String, var fontSize: Double, var fontFamily: String) : AbstractVisualObject(),
|
||||
VisualObject3D {
|
||||
@Serializable(ConfigSerializer::class)
|
||||
|
@ -7,16 +7,29 @@ import hep.dataforge.names.plus
|
||||
import hep.dataforge.values.ValueType
|
||||
import hep.dataforge.vis.common.Colors
|
||||
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_COLOR_KEY
|
||||
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_KEY
|
||||
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_OPACITY_KEY
|
||||
|
||||
class Material3D(override val config: Config) : Specific {
|
||||
|
||||
/**
|
||||
* Primary web-color for the material
|
||||
*/
|
||||
var color by string(key = COLOR_KEY)
|
||||
|
||||
var specularColor by string()
|
||||
/**
|
||||
* Specular color for phong material
|
||||
*/
|
||||
var specularColor by string(key = SPECULAR_COLOR_KEY)
|
||||
|
||||
/**
|
||||
* Opacity
|
||||
*/
|
||||
var opacity by float(1f, key = OPACITY_KEY)
|
||||
|
||||
/**
|
||||
* Replace material by wire frame
|
||||
*/
|
||||
var wireframe by boolean(false, WIREFRAME_KEY)
|
||||
|
||||
companion object : Specification<Material3D> {
|
||||
@ -25,7 +38,7 @@ class Material3D(override val config: Config) : Specific {
|
||||
val MATERIAL_KEY = "material".asName()
|
||||
internal val COLOR_KEY = "color".asName()
|
||||
val MATERIAL_COLOR_KEY = MATERIAL_KEY + COLOR_KEY
|
||||
val SPECULAR_COLOR = "specularColor".asName()
|
||||
val SPECULAR_COLOR_KEY = "specularColor".asName()
|
||||
internal val OPACITY_KEY = "opacity".asName()
|
||||
val MATERIAL_OPACITY_KEY = MATERIAL_KEY + OPACITY_KEY
|
||||
internal val WIREFRAME_KEY = "wireframe".asName()
|
||||
@ -54,10 +67,16 @@ class Material3D(override val config: Config) : Specific {
|
||||
}
|
||||
}
|
||||
|
||||
fun VisualObject3D.color(rgb: String) {
|
||||
setProperty(MATERIAL_COLOR_KEY, rgb)
|
||||
/**
|
||||
* Set color as web-color
|
||||
*/
|
||||
fun VisualObject3D.color(webColor: String) {
|
||||
setProperty(MATERIAL_COLOR_KEY, webColor)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set color as integer
|
||||
*/
|
||||
fun VisualObject3D.color(rgb: Int) {
|
||||
setProperty(MATERIAL_COLOR_KEY, rgb)
|
||||
}
|
||||
@ -76,16 +95,15 @@ var VisualObject3D.color: String?
|
||||
setProperty(MATERIAL_COLOR_KEY, value)
|
||||
}
|
||||
|
||||
//var VisualObject3D.material: Material3D?
|
||||
// get() = getProperty(MATERIAL_KEY).node?.let { Material3D.wrap(it) }
|
||||
// set(value) = setProperty(MATERIAL_KEY, value?.config)
|
||||
val VisualObject3D.material: Material3D?
|
||||
get() = getProperty(MATERIAL_KEY).node?.let { Material3D.wrap(it) }
|
||||
|
||||
fun VisualObject3D.material(builder: Material3D.() -> Unit) {
|
||||
val node = config[Material3D.MATERIAL_KEY].node
|
||||
val node = config[MATERIAL_KEY].node
|
||||
if (node != null) {
|
||||
Material3D.update(node, builder)
|
||||
} else {
|
||||
config[Material3D.MATERIAL_KEY] = Material3D(builder)
|
||||
config[MATERIAL_KEY] = Material3D(builder)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,12 +4,17 @@ package hep.dataforge.vis.spatial
|
||||
|
||||
import hep.dataforge.io.serialization.ConfigSerializer
|
||||
import hep.dataforge.meta.Config
|
||||
import hep.dataforge.meta.number
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.names.plus
|
||||
import hep.dataforge.vis.common.AbstractVisualObject
|
||||
import hep.dataforge.vis.common.number
|
||||
import hep.dataforge.vis.common.set
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
|
||||
@Serializable
|
||||
@SerialName("3d.line")
|
||||
class PolyLine(var points: List<Point3D>) : AbstractVisualObject(), VisualObject3D {
|
||||
@Serializable(ConfigSerializer::class)
|
||||
override var properties: Config? = null
|
||||
@ -19,7 +24,11 @@ class PolyLine(var points: List<Point3D>) : AbstractVisualObject(), VisualObject
|
||||
override var scale: Point3D? = null
|
||||
|
||||
//var lineType by string()
|
||||
var thickness by number(1.0, key = "material.thickness")
|
||||
var thickness by number(1.0, key = Material3D.MATERIAL_KEY + THICKNESS_KEY)
|
||||
|
||||
companion object {
|
||||
val THICKNESS_KEY = "thickness".asName()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -26,7 +26,11 @@ import kotlin.collections.set
|
||||
*/
|
||||
@Serializable
|
||||
@SerialName("3d.proxy")
|
||||
class Proxy(val templateName: Name) : AbstractVisualObject(), VisualGroup, VisualObject3D {
|
||||
class Proxy private constructor(val templateName: Name) : AbstractVisualObject(), VisualGroup, VisualObject3D {
|
||||
|
||||
constructor(parent: VisualGroup3D, templateName: Name) : this(templateName) {
|
||||
this.parent = parent
|
||||
}
|
||||
|
||||
override var position: Point3D? = null
|
||||
override var rotation: Point3D? = null
|
||||
@ -59,7 +63,7 @@ class Proxy(val templateName: Name) : AbstractVisualObject(), VisualGroup, Visua
|
||||
}
|
||||
|
||||
override val children: Map<NameToken, ProxyChild>
|
||||
get() = (prototype as? MutableVisualGroup)?.children
|
||||
get() = (prototype as? VisualGroup)?.children
|
||||
?.filter { !it.key.toString().startsWith("@") }
|
||||
?.mapValues {
|
||||
ProxyChild(it.key.asName())
|
||||
@ -79,9 +83,12 @@ class Proxy(val templateName: Name) : AbstractVisualObject(), VisualGroup, Visua
|
||||
|
||||
override fun allProperties(): Laminate = Laminate(properties, mergedStyles, prototype.allProperties())
|
||||
|
||||
override fun attachChildren() {
|
||||
//do nothing
|
||||
}
|
||||
|
||||
//override fun findAllStyles(): Laminate = Laminate((styles + prototype.styles).mapNotNull { findStyle(it) })
|
||||
|
||||
@Serializable
|
||||
inner class ProxyChild(val name: Name) : AbstractVisualObject(), VisualGroup {
|
||||
|
||||
val prototype: VisualObject get() = prototypeFor(name)
|
||||
@ -125,6 +132,9 @@ class Proxy(val templateName: Name) : AbstractVisualObject(), VisualGroup, Visua
|
||||
}
|
||||
}
|
||||
|
||||
override fun attachChildren() {
|
||||
//do nothing
|
||||
}
|
||||
|
||||
override fun allProperties(): Laminate = Laminate(properties, mergedStyles, prototype.allProperties())
|
||||
|
||||
@ -149,7 +159,7 @@ inline fun VisualGroup3D.ref(
|
||||
templateName: Name,
|
||||
name: String = "",
|
||||
block: Proxy.() -> Unit = {}
|
||||
) = Proxy(templateName).apply(block).also { set(name, it) }
|
||||
) = Proxy(this, templateName).apply(block).also { set(name, it) }
|
||||
|
||||
/**
|
||||
* Add new proxy wrapping given object and automatically adding it to the prototypes
|
||||
@ -162,7 +172,9 @@ fun VisualGroup3D.proxy(
|
||||
): Proxy {
|
||||
val existing = getPrototype(templateName)
|
||||
if (existing == null) {
|
||||
setPrototype(templateName, obj)
|
||||
prototypes {
|
||||
this[templateName] = obj
|
||||
}
|
||||
} else if (existing != obj) {
|
||||
error("Can't add different prototype on top of existing one")
|
||||
}
|
||||
|
@ -5,6 +5,8 @@ package hep.dataforge.vis.spatial
|
||||
import hep.dataforge.io.serialization.ConfigSerializer
|
||||
import hep.dataforge.meta.Config
|
||||
import hep.dataforge.vis.common.AbstractVisualObject
|
||||
import hep.dataforge.vis.common.set
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import kotlin.math.PI
|
||||
@ -12,6 +14,7 @@ import kotlin.math.cos
|
||||
import kotlin.math.sin
|
||||
|
||||
@Serializable
|
||||
@SerialName("3d.sphere")
|
||||
class Sphere(
|
||||
var radius: Float,
|
||||
var phiStart: Float = 0f,
|
||||
|
@ -4,6 +4,8 @@ package hep.dataforge.vis.spatial
|
||||
import hep.dataforge.io.serialization.ConfigSerializer
|
||||
import hep.dataforge.meta.Config
|
||||
import hep.dataforge.vis.common.AbstractVisualObject
|
||||
import hep.dataforge.vis.common.set
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import kotlin.math.PI
|
||||
@ -14,6 +16,7 @@ import kotlin.math.sin
|
||||
* Straight tube segment
|
||||
*/
|
||||
@Serializable
|
||||
@SerialName("3d.tube")
|
||||
class Tube(
|
||||
var radius: Float,
|
||||
var height: Float,
|
||||
|
@ -9,29 +9,32 @@ import hep.dataforge.io.serialization.MetaSerializer
|
||||
import hep.dataforge.io.serialization.NameSerializer
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.toName
|
||||
import hep.dataforge.vis.common.Visual
|
||||
import hep.dataforge.vis.common.VisualObject
|
||||
import hep.dataforge.vis.common.VisualPlugin
|
||||
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 Visual3DPlugin(meta: Meta) : AbstractPlugin(meta) {
|
||||
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> {
|
||||
return if (target == VisualPlugin.VISUAL_FACTORY_TYPE) {
|
||||
mapOf()
|
||||
} else {
|
||||
emptyMap()
|
||||
}
|
||||
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<Visual3DPlugin> {
|
||||
|
||||
companion object : PluginFactory<Visual3D> {
|
||||
override val tag: PluginTag = PluginTag(name = "visual.spatial", group = PluginTag.DATAFORGE_GROUP)
|
||||
override val type: KClass<out Visual3DPlugin> = Visual3DPlugin::class
|
||||
override fun invoke(meta: Meta, context: Context): Visual3DPlugin = Visual3DPlugin(meta)
|
||||
override val type: KClass<out Visual3D> = Visual3D::class
|
||||
override fun invoke(meta: Meta, context: Context): Visual3D = Visual3D(meta)
|
||||
|
||||
val serialModule = SerializersModule {
|
||||
contextual(Point3DSerializer)
|
@ -19,6 +19,7 @@ import hep.dataforge.names.isEmpty
|
||||
import hep.dataforge.vis.common.AbstractVisualGroup
|
||||
import hep.dataforge.vis.common.StyleSheet
|
||||
import hep.dataforge.vis.common.VisualObject
|
||||
import hep.dataforge.vis.common.set
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
@ -30,19 +31,19 @@ import kotlin.collections.set
|
||||
@Serializable
|
||||
@SerialName("group.3d")
|
||||
class VisualGroup3D : AbstractVisualGroup(), VisualObject3D {
|
||||
|
||||
override var styleSheet: StyleSheet? = null
|
||||
private set
|
||||
|
||||
/**
|
||||
* A container for templates visible inside this group
|
||||
*/
|
||||
@SerialName(PROTOTYPES_KEY)
|
||||
var prototypes: VisualGroup3D? = null
|
||||
set(value) {
|
||||
value?.parent = this
|
||||
field = value
|
||||
}
|
||||
|
||||
override var styleSheet: StyleSheet? = null
|
||||
private set
|
||||
|
||||
//FIXME to be lifted to AbstractVisualGroup after https://github.com/Kotlin/kotlinx.serialization/issues/378 is fixed
|
||||
override var properties: Config? = null
|
||||
|
||||
@ -54,28 +55,34 @@ class VisualGroup3D : AbstractVisualGroup(), VisualObject3D {
|
||||
private val _children = HashMap<NameToken, VisualObject>()
|
||||
override val children: Map<NameToken, VisualObject> get() = _children
|
||||
|
||||
init {
|
||||
//Do after deserialization
|
||||
attachChildren()
|
||||
// init {
|
||||
// //Do after deserialization
|
||||
// attachChildren()
|
||||
// }
|
||||
|
||||
override fun attachChildren() {
|
||||
prototypes?.parent = this
|
||||
prototypes?.attachChildren()
|
||||
super.attachChildren()
|
||||
}
|
||||
|
||||
/**
|
||||
* Update or create stylesheet
|
||||
*/
|
||||
fun styleSheet(block: StyleSheet.() -> Unit) {
|
||||
val res = this.styleSheet ?: StyleSheet(this).also { this.styleSheet = it }
|
||||
val res = styleSheet ?: StyleSheet(this).also { styleSheet = it }
|
||||
res.block()
|
||||
}
|
||||
|
||||
override fun removeChild(token: NameToken) {
|
||||
_children.remove(token)
|
||||
_children.remove(token)?.run { parent = null }
|
||||
childrenChanged(token.asName(), null)
|
||||
}
|
||||
|
||||
override fun setChild(token: NameToken, child: VisualObject) {
|
||||
if (child.parent == null) {
|
||||
child.parent = this
|
||||
} else {
|
||||
} else if (child.parent !== this) {
|
||||
error("Can't reassign existing parent for $child")
|
||||
}
|
||||
_children[token] = child
|
||||
@ -102,42 +109,32 @@ class VisualGroup3D : AbstractVisualGroup(), VisualObject3D {
|
||||
}
|
||||
}
|
||||
|
||||
override fun attachChildren() {
|
||||
super.attachChildren()
|
||||
prototypes?.run {
|
||||
parent = this
|
||||
attachChildren()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val PROTOTYPES_KEY = "templates"
|
||||
// val PROTOTYPES_KEY = NameToken("@prototypes")
|
||||
|
||||
fun fromJson(json: String): VisualGroup3D =
|
||||
Visual3D.json.parse(serializer(), json).also { it.attachChildren() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ger a prototype redirecting the request to the parent if prototype is not found
|
||||
*/
|
||||
fun VisualGroup3D.getPrototype(name: Name): VisualObject3D? =
|
||||
tailrec fun VisualGroup3D.getPrototype(name: Name): VisualObject3D? =
|
||||
prototypes?.get(name) as? VisualObject3D ?: (parent as? VisualGroup3D)?.getPrototype(name)
|
||||
|
||||
/**
|
||||
* Defined a prototype inside current group
|
||||
* Create or edit prototype node as a group
|
||||
*/
|
||||
fun VisualGroup3D.setPrototype(name: Name, obj: VisualObject3D) {
|
||||
(prototypes ?: VisualGroup3D().also { this.prototypes = it })[name] = obj
|
||||
inline fun VisualGroup3D.prototypes(builder: VisualGroup3D.() -> Unit): Unit {
|
||||
(prototypes ?: VisualGroup3D().also { prototypes = it }).run(builder)
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a group with given [key], attach it to this parent and return it.
|
||||
*/
|
||||
fun VisualGroup3D.group(key: String = "", action: VisualGroup3D.() -> Unit = {}): VisualGroup3D =
|
||||
VisualGroup3D().apply(action).also { set(key, it) }
|
||||
|
||||
/**
|
||||
* Create or edit prototype node as a group
|
||||
*/
|
||||
inline fun VisualGroup3D.prototypes(builder: VisualGroup3D.() -> Unit): Unit {
|
||||
(prototypes ?: VisualGroup3D().also { this.prototypes = it }).run(builder)
|
||||
}
|
||||
VisualGroup3D().apply(action).also {
|
||||
set(key, it)
|
||||
}
|
||||
|
||||
|
@ -22,22 +22,10 @@ interface VisualObject3D : VisualObject {
|
||||
var rotation: Point3D?
|
||||
var scale: Point3D?
|
||||
|
||||
fun MetaBuilder.updatePosition() {
|
||||
xPos to position?.x
|
||||
yPos to position?.y
|
||||
zPos to position?.z
|
||||
xRotation to rotation?.x
|
||||
yRotation to rotation?.y
|
||||
zRotation to rotation?.z
|
||||
xScale to scale?.x
|
||||
yScale to scale?.y
|
||||
zScale to scale?.z
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
val VISIBLE_KEY = "visible".asName()
|
||||
val SELECTED_KEY = "selected".asName()
|
||||
// val SELECTED_KEY = "selected".asName()
|
||||
val DETAIL_KEY = "detail".asName()
|
||||
val LAYER_KEY = "layer".asName()
|
||||
val IGNORE_KEY = "ignore".asName()
|
||||
|
@ -46,8 +46,8 @@ object Point3DSerializer : KSerializer<Point3D> {
|
||||
when (val i = decodeElementIndex(descriptor)) {
|
||||
CompositeDecoder.READ_DONE -> break@loop
|
||||
0 -> x = decodeNullableSerializableElement(descriptor, 0, DoubleSerializer.nullable) ?: 0.0
|
||||
1 -> y = decodeNullableSerializableElement(descriptor, 0, DoubleSerializer.nullable) ?: 0.0
|
||||
2 -> z = decodeNullableSerializableElement(descriptor, 0, DoubleSerializer.nullable) ?: 0.0
|
||||
1 -> y = decodeNullableSerializableElement(descriptor, 1, DoubleSerializer.nullable) ?: 0.0
|
||||
2 -> z = decodeNullableSerializableElement(descriptor, 2, DoubleSerializer.nullable) ?: 0.0
|
||||
else -> throw SerializationException("Unknown index $i")
|
||||
}
|
||||
}
|
||||
@ -79,7 +79,7 @@ object Point2DSerializer : KSerializer<Point2D> {
|
||||
when (val i = decodeElementIndex(descriptor)) {
|
||||
CompositeDecoder.READ_DONE -> break@loop
|
||||
0 -> x = decodeNullableSerializableElement(descriptor, 0, DoubleSerializer.nullable) ?: 0.0
|
||||
1 -> y = decodeNullableSerializableElement(descriptor, 0, DoubleSerializer.nullable) ?: 0.0
|
||||
1 -> y = decodeNullableSerializableElement(descriptor, 1, DoubleSerializer.nullable) ?: 0.0
|
||||
else -> throw SerializationException("Unknown index $i")
|
||||
}
|
||||
}
|
@ -51,7 +51,9 @@ object RemoveSingleChild : VisualTreeTransform<VisualGroup3D>() {
|
||||
}
|
||||
|
||||
replaceChildren()
|
||||
prototypes?.replaceChildren()
|
||||
prototypes {
|
||||
replaceChildren()
|
||||
}
|
||||
}
|
||||
|
||||
override fun VisualGroup3D.clone(): VisualGroup3D {
|
||||
|
@ -6,6 +6,7 @@ import hep.dataforge.vis.common.MutableVisualGroup
|
||||
import hep.dataforge.vis.common.VisualGroup
|
||||
import hep.dataforge.vis.spatial.Proxy
|
||||
import hep.dataforge.vis.spatial.VisualGroup3D
|
||||
import hep.dataforge.vis.spatial.prototypes
|
||||
|
||||
object UnRef : VisualTreeTransform<VisualGroup3D>() {
|
||||
private fun VisualGroup.countRefs(): Map<Name, Int> {
|
||||
|
@ -25,7 +25,7 @@ class ConvexTest {
|
||||
|
||||
val convex = group.first() as Convex
|
||||
|
||||
val json = Visual3DPlugin.json.toJson(Convex.serializer(), convex)
|
||||
val json = Visual3D.json.toJson(Convex.serializer(), convex)
|
||||
val meta = json.toMeta()
|
||||
|
||||
val points = meta.getIndexed("points").values.map { (it as MetaItem.NodeItem<*>).node.point3D()}
|
||||
|
@ -1,6 +1,6 @@
|
||||
package hep.dataforge.vis.spatial
|
||||
|
||||
import hep.dataforge.vis.spatial.Visual3DPlugin.Companion.json
|
||||
import hep.dataforge.vis.spatial.Visual3D.Companion.json
|
||||
import kotlinx.serialization.ImplicitReflectionSerializer
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
@ -31,12 +31,14 @@ interface ThreeFactory<in T : VisualObject> {
|
||||
/**
|
||||
* Update position, rotation and visibility
|
||||
*/
|
||||
fun Object3D.updatePosition(obj: VisualObject3D) {
|
||||
fun Object3D.updatePosition(obj: VisualObject) {
|
||||
visible = obj.visible ?: true
|
||||
position.set(obj.x, obj.y, obj.z)
|
||||
setRotationFromEuler(obj.euler)
|
||||
scale.set(obj.scaleX, obj.scaleY, obj.scaleZ)
|
||||
updateMatrix()
|
||||
if(obj is VisualObject3D) {
|
||||
position.set(obj.x, obj.y, obj.z)
|
||||
setRotationFromEuler(obj.euler)
|
||||
scale.set(obj.scaleX, obj.scaleY, obj.scaleZ)
|
||||
updateMatrix()
|
||||
}
|
||||
}
|
||||
|
||||
///**
|
||||
@ -54,7 +56,7 @@ fun Object3D.updatePosition(obj: VisualObject3D) {
|
||||
/**
|
||||
* Update non-position non-geometry property
|
||||
*/
|
||||
fun Object3D.updateProperty(source: VisualObject3D, propertyName: Name) {
|
||||
fun Object3D.updateProperty(source: VisualObject, propertyName: Name) {
|
||||
if (this is Mesh && propertyName.startsWith(MATERIAL_KEY)) {
|
||||
this.material = getMaterial(source)
|
||||
} else if (
|
||||
|
@ -25,7 +25,7 @@ object ThreeLabelFactory : ThreeFactory<Label3D> {
|
||||
} )
|
||||
return Mesh(textGeo, getMaterial(obj)).apply {
|
||||
updatePosition(obj)
|
||||
obj.onPropertyChange(this@ThreeLabelFactory){name: Name, before: MetaItem<*>?, after: MetaItem<*>? ->
|
||||
obj.onPropertyChange(this@ThreeLabelFactory){ _: Name, _: MetaItem<*>?, _: MetaItem<*>? ->
|
||||
//TODO
|
||||
}
|
||||
}
|
||||
|
@ -18,11 +18,10 @@ object ThreeLineFactory : ThreeFactory<PolyLine> {
|
||||
vertices = obj.points.toTypedArray()
|
||||
}
|
||||
|
||||
val material =
|
||||
ThreeMaterials.getLineMaterial(obj.getProperty(MeshThreeFactory.EDGES_MATERIAL_KEY).node)
|
||||
val material = ThreeMaterials.getLineMaterial(obj.getProperty(MeshThreeFactory.EDGES_MATERIAL_KEY).node)
|
||||
|
||||
material.linewidth = obj.thickness.toDouble()
|
||||
material.color = obj.color?.let { Color(it) }?: DEFAULT_LINE_COLOR
|
||||
material.color = obj.color?.let { Color(it) } ?: DEFAULT_LINE_COLOR
|
||||
|
||||
return LineSegments(geometry, material).apply {
|
||||
updatePosition(obj)
|
||||
|
@ -3,8 +3,8 @@ package hep.dataforge.vis.spatial.three
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.values.ValueType
|
||||
import hep.dataforge.vis.common.Colors
|
||||
import hep.dataforge.vis.common.VisualObject
|
||||
import hep.dataforge.vis.spatial.Material3D
|
||||
import hep.dataforge.vis.spatial.VisualObject3D
|
||||
import info.laht.threekt.materials.LineBasicMaterial
|
||||
import info.laht.threekt.materials.Material
|
||||
import info.laht.threekt.materials.MeshBasicMaterial
|
||||
@ -37,12 +37,12 @@ object ThreeMaterials {
|
||||
}
|
||||
}
|
||||
|
||||
fun getMaterial(visualObject3D: VisualObject3D): Material {
|
||||
fun getMaterial(visualObject3D: VisualObject): Material {
|
||||
val meta = visualObject3D.getProperty(Material3D.MATERIAL_KEY).node ?: return ThreeMaterials.DEFAULT
|
||||
return if (meta[Material3D.SPECULAR_COLOR] != null) {
|
||||
return if (meta[Material3D.SPECULAR_COLOR_KEY] != null) {
|
||||
MeshPhongMaterial().apply {
|
||||
color = meta[Material3D.COLOR_KEY]?.getColor() ?: DEFAULT_COLOR
|
||||
specular = meta[Material3D.SPECULAR_COLOR]!!.getColor()
|
||||
specular = meta[Material3D.SPECULAR_COLOR_KEY]!!.getColor()
|
||||
opacity = meta[Material3D.OPACITY_KEY]?.double ?: 1.0
|
||||
transparent = opacity < 1.0
|
||||
wireframe = meta[Material3D.WIREFRAME_KEY].boolean ?: false
|
||||
|
@ -74,8 +74,8 @@ class ThreePlugin : AbstractPlugin() {
|
||||
return@onChildrenChange
|
||||
}
|
||||
|
||||
val parentName = name.cutLast()
|
||||
val childName = name.last()!!
|
||||
// val parentName = name.cutLast()
|
||||
// val childName = name.last()!!
|
||||
|
||||
//removing old object
|
||||
findChild(name)?.let { oldChild ->
|
||||
|
@ -25,7 +25,7 @@ class ThreeProxyFactory(val three: ThreePlugin) : ThreeFactory<Proxy> {
|
||||
if (name.first()?.body == PROXY_CHILD_PROPERTY_PREFIX) {
|
||||
val childName = name.first()?.index?.toName() ?: error("Wrong syntax for proxy child property: '$name'")
|
||||
val propertyName = name.cutFirst()
|
||||
val proxyChild = obj[childName] as? VisualObject3D ?: error("Proxy child with name '$childName' not found or not a 3D object")
|
||||
val proxyChild = obj[childName] ?: error("Proxy child with name '$childName' not found")
|
||||
val child = object3D.findChild(childName)?: error("Object child with name '$childName' not found")
|
||||
child.updateProperty(proxyChild, propertyName)
|
||||
} else {
|
||||
|
@ -1,8 +1,8 @@
|
||||
package hep.dataforge.vis.spatial.three
|
||||
|
||||
import hep.dataforge.js.requireJS
|
||||
import hep.dataforge.vis.js.editor.card
|
||||
import hep.dataforge.vis.spatial.Visual3DPlugin
|
||||
import hep.dataforge.vis.js.editor.accordion
|
||||
import hep.dataforge.vis.spatial.Visual3D
|
||||
import hep.dataforge.vis.spatial.VisualGroup3D
|
||||
import kotlinx.html.InputType
|
||||
import kotlinx.html.TagConsumer
|
||||
@ -28,7 +28,64 @@ private fun saveData(event: Event, fileName: String, mimeType: String = "text/pl
|
||||
fun Element.displayCanvasControls(canvas: ThreeCanvas, block: TagConsumer<HTMLElement>.() -> Unit = {}) {
|
||||
clear()
|
||||
append {
|
||||
card("Settings") {
|
||||
accordion("controls") {
|
||||
entry("Settings") {
|
||||
div("row") {
|
||||
div("col-2") {
|
||||
label("checkbox-inline") {
|
||||
input(type = InputType.checkBox).apply {
|
||||
checked = canvas.axes.visible
|
||||
onChangeFunction = {
|
||||
canvas.axes.visible = checked
|
||||
}
|
||||
}
|
||||
+"Axes"
|
||||
}
|
||||
}
|
||||
div("col-1") {
|
||||
button {
|
||||
+"Export"
|
||||
onClickFunction = {
|
||||
val json = (canvas.content as? VisualGroup3D)?.let { group ->
|
||||
Visual3D.json.stringify(
|
||||
VisualGroup3D.serializer(),
|
||||
group
|
||||
)
|
||||
}
|
||||
if (json != null) {
|
||||
saveData(it, "object.json", "text/json") {
|
||||
json
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
entry("Layers") {
|
||||
div("row") {
|
||||
(0..11).forEach { layer ->
|
||||
div("col-1") {
|
||||
label { +layer.toString() }
|
||||
input(type = InputType.checkBox).apply {
|
||||
if (layer == 0) {
|
||||
checked = true
|
||||
}
|
||||
onChangeFunction = {
|
||||
if (checked) {
|
||||
canvas.camera.layers.enable(layer)
|
||||
} else {
|
||||
canvas.camera.layers.disable(layer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* card("Settings") {
|
||||
div("row") {
|
||||
div("col-2") {
|
||||
label("checkbox-inline") {
|
||||
@ -46,7 +103,7 @@ fun Element.displayCanvasControls(canvas: ThreeCanvas, block: TagConsumer<HTMLEl
|
||||
+"Export"
|
||||
onClickFunction = {
|
||||
val json = (canvas.content as? VisualGroup3D)?.let { group ->
|
||||
Visual3DPlugin.json.stringify(
|
||||
Visual3D.json.stringify(
|
||||
VisualGroup3D.serializer(),
|
||||
group
|
||||
)
|
||||
@ -81,7 +138,7 @@ fun Element.displayCanvasControls(canvas: ThreeCanvas, block: TagConsumer<HTMLEl
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
block()
|
||||
}
|
||||
}
|
@ -66,7 +66,7 @@ fun MetaItem<*>?.material(): Material {
|
||||
is MetaItem.NodeItem -> PhongMaterial().apply {
|
||||
val opacity = node[Material3D.OPACITY_KEY].double ?: 1.0
|
||||
diffuseColor = node[Material3D.COLOR_KEY]?.color(opacity) ?: Color.DARKGREY
|
||||
specularColor = node[Material3D.SPECULAR_COLOR]?.color(opacity) ?: Color.WHITE
|
||||
specularColor = node[Material3D.SPECULAR_COLOR_KEY]?.color(opacity) ?: Color.WHITE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,8 +13,8 @@ class FXProxyFactory(val plugin: FX3DPlugin) : FX3DFactory<Proxy> {
|
||||
override val type: KClass<in Proxy> get() = Proxy::class
|
||||
|
||||
override fun invoke(obj: Proxy, binding: VisualObjectFXBinding): Node {
|
||||
val template = obj.prototype
|
||||
val node = plugin.buildNode(template)
|
||||
val prototype = obj.prototype
|
||||
val node = plugin.buildNode(prototype)
|
||||
|
||||
obj.onPropertyChange(this) { name, _, _ ->
|
||||
if (name.first()?.body == Proxy.PROXY_CHILD_PROPERTY_PREFIX) {
|
||||
|
@ -1,12 +1,13 @@
|
||||
package hep.dataforge.vis.spatial
|
||||
import org.fxyz3d.geometry.Point3D as FXPoint3D
|
||||
|
||||
actual data class Point2D(actual var x: Double, actual var y: Double) {
|
||||
actual constructor(x: Number, y: Number) : this(x.toDouble(), y.toDouble())
|
||||
}
|
||||
|
||||
actual class Point3D(val point: org.fxyz3d.geometry.Point3D) {
|
||||
actual class Point3D(val point: FXPoint3D) {
|
||||
actual constructor(x: Number, y: Number, z: Number) : this(
|
||||
org.fxyz3d.geometry.Point3D(
|
||||
FXPoint3D(
|
||||
x.toFloat(),
|
||||
y.toFloat(),
|
||||
z.toFloat()
|
||||
@ -32,7 +33,7 @@ actual class Point3D(val point: org.fxyz3d.geometry.Point3D) {
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return this.point == (other as? hep.dataforge.vis.spatial.Point3D)?.point
|
||||
return this.point == (other as? Point3D)?.point
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
|
@ -27,6 +27,11 @@ kotlin {
|
||||
api(project(":dataforge-vis-spatial-gdml"))
|
||||
}
|
||||
}
|
||||
jvmMain{
|
||||
dependencies {
|
||||
api("org.fxyz3d:fxyz3d:0.5.2")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,54 @@
|
||||
package hep.dataforge.vis.spatial.gdml.demo
|
||||
|
||||
import scientifik.gdml.*
|
||||
|
||||
|
||||
fun cubes(): GDML = GDML {
|
||||
val center = define.position("center")
|
||||
structure {
|
||||
val air = ref<GDMLMaterial>("G4_AIR")
|
||||
val tubeMaterial = ref<GDMLMaterial>("tube")
|
||||
val boxMaterial = ref<GDMLMaterial>("box")
|
||||
val segment = solids.tube("segment", 20, 5.0) {
|
||||
rmin = 17
|
||||
deltaphi = 60
|
||||
aunit = DEG
|
||||
}
|
||||
val worldBox = solids.box("LargeBox", 200, 200, 200)
|
||||
val smallBox = solids.box("smallBox", 30, 30, 30)
|
||||
val segmentVolume = volume("segment", tubeMaterial, segment.ref()) {}
|
||||
val circle = volume("composite", boxMaterial, smallBox.ref()) {
|
||||
for (i in 0 until 6) {
|
||||
physVolume(segmentVolume) {
|
||||
name = "segment$i"
|
||||
positionref = center.ref()
|
||||
rotation {
|
||||
z = 60 * i
|
||||
unit = DEG
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
world = volume("world", air, worldBox.ref()) {
|
||||
for (i in 0 until 3) {
|
||||
for (j in 0 until 3) {
|
||||
for (k in 0 until 3) {
|
||||
physVolume(circle) {
|
||||
name = "composite$i$j$k"
|
||||
position {
|
||||
x = (-50 + i * 50)
|
||||
y = (-50 + j * 50)
|
||||
z = (-50 + k * 50)
|
||||
}
|
||||
rotation {
|
||||
x = i * 120
|
||||
y = j * 120
|
||||
z = 120 * k
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package hep.dataforge.vis.spatial.gdml
|
||||
|
||||
import hep.dataforge.meta.string
|
||||
import hep.dataforge.names.toName
|
||||
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")
|
||||
assertEquals("red", visual["composite000.segment0".toName()]?.getProperty(Material3D.MATERIAL_COLOR_KEY).string)
|
||||
}
|
||||
}
|
@ -14,7 +14,6 @@ import hep.dataforge.vis.js.editor.displayPropertyEditor
|
||||
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.Visual3DPlugin
|
||||
import hep.dataforge.vis.spatial.VisualGroup3D
|
||||
import hep.dataforge.vis.spatial.VisualObject3D
|
||||
import hep.dataforge.vis.spatial.VisualObject3D.Companion.VISIBLE_KEY
|
||||
@ -25,11 +24,7 @@ import hep.dataforge.vis.spatial.three.ThreePlugin
|
||||
import hep.dataforge.vis.spatial.three.displayCanvasControls
|
||||
import hep.dataforge.vis.spatial.three.output
|
||||
import hep.dataforge.vis.spatial.visible
|
||||
import kotlinx.html.dom.append
|
||||
import kotlinx.html.js.p
|
||||
import org.w3c.dom.DragEvent
|
||||
import org.w3c.dom.HTMLDivElement
|
||||
import org.w3c.dom.HTMLElement
|
||||
import org.w3c.dom.*
|
||||
import org.w3c.files.FileList
|
||||
import org.w3c.files.FileReader
|
||||
import org.w3c.files.get
|
||||
@ -81,17 +76,18 @@ private class GDMLDemoApp : Application {
|
||||
}
|
||||
|
||||
private fun message(message: String?) {
|
||||
document.getElementById("messages")?.let { element ->
|
||||
if (message == null) {
|
||||
element.clear()
|
||||
} else {
|
||||
element.append {
|
||||
p {
|
||||
+message
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log(message)
|
||||
// document.getElementById("messages")?.let { element ->
|
||||
// if (message == null) {
|
||||
// element.clear()
|
||||
// } else {
|
||||
// element.append {
|
||||
// p {
|
||||
// +message
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
private val gdmlConfiguration: GDMLTransformer.() -> Unit = {
|
||||
@ -128,7 +124,7 @@ private class GDMLDemoApp : Application {
|
||||
//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("layers") ?: error("Element with id 'layers' 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()
|
||||
@ -136,15 +132,14 @@ private class GDMLDemoApp : Application {
|
||||
val action: (name: String, data: String) -> Unit = { name, data ->
|
||||
canvasElement.clear()
|
||||
spinner(true)
|
||||
message("Loading GDML")
|
||||
val gdml = GDML.format.parse(GDML.serializer(), data)
|
||||
message("Converting GDML into DF-VIS format")
|
||||
|
||||
val visual: VisualObject3D = when {
|
||||
name.endsWith(".gdml") || name.endsWith(".xml") -> gdml.toVisual(gdmlConfiguration)
|
||||
name.endsWith(".json") -> {
|
||||
Visual3DPlugin.json.parse(VisualGroup3D.serializer(), data).apply { attachChildren() }
|
||||
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.fromJson(data)
|
||||
else -> {
|
||||
window.alert("File extension is not recognized: $name")
|
||||
error("File extension is not recognized: $name")
|
||||
@ -187,9 +182,9 @@ private class GDMLDemoApp : Application {
|
||||
// canvas.clickListener = ::selectElement
|
||||
|
||||
//tree.visualObjectTree(visual, editor::propertyEditor)
|
||||
treeElement.displayObjectTree(visual) { name ->
|
||||
selectElement(name)
|
||||
canvas.highlight(name)
|
||||
treeElement.displayObjectTree(visual) { treeName ->
|
||||
selectElement(treeName)
|
||||
canvas.highlight(treeName)
|
||||
}
|
||||
canvas.render(visual)
|
||||
|
||||
@ -204,6 +199,19 @@ private class GDMLDemoApp : Application {
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,7 @@
|
||||
.drop_zone {
|
||||
outline: 1px solid orange;
|
||||
}
|
||||
|
||||
.loader {
|
||||
border: 16px solid #f3f3f3; /* Light grey */
|
||||
border-top: 16px solid #3498db; /* Blue */
|
||||
|
@ -4,34 +4,45 @@
|
||||
<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"
|
||||
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="css/main.css">
|
||||
<link rel="stylesheet" href="css/jsoneditor.min.css">
|
||||
<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/main.css">
|
||||
</head>
|
||||
<body class="testApp">
|
||||
<div class="container" id="drop_zone" data-toggle="tooltip" data-placement="right"
|
||||
title="To load data in text format, drag-and-drop file here">
|
||||
Load data
|
||||
<br/>
|
||||
(drag file here)
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<h1>GDML demo</h1>
|
||||
</div>
|
||||
<!--<div class="container loader" id="loader" style="display:none;"></div>-->
|
||||
<div class="container animate-bottom" id="messages"></div>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-lg-3" id="tree"></div>
|
||||
<div class="col-lg-6">
|
||||
<div class="row" id="layers"></div>
|
||||
<div class="row container" id="canvas"></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 class="col-lg-3" id="editor"></div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
<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>
|
||||
</html>
|
@ -4,15 +4,13 @@ import hep.dataforge.context.Global
|
||||
import hep.dataforge.vis.fx.editor.VisualObjectEditorFragment
|
||||
import hep.dataforge.vis.fx.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.vis.spatial.gdml.LUnit
|
||||
import hep.dataforge.vis.spatial.gdml.readFile
|
||||
import hep.dataforge.vis.spatial.gdml.toVisual
|
||||
import javafx.geometry.Orientation
|
||||
import javafx.scene.Parent
|
||||
import javafx.stage.FileChooser
|
||||
import scientifik.gdml.GDML
|
||||
import tornadofx.*
|
||||
|
||||
class GDMLDemoApp : App(GDMLView::class)
|
||||
@ -36,26 +34,12 @@ class GDMLView : View() {
|
||||
override val root: Parent = borderpane {
|
||||
top {
|
||||
buttonbar {
|
||||
button("Load GDML") {
|
||||
button("Load GDML/json") {
|
||||
action {
|
||||
val file = chooseFile("Select a GDML file", filters = gdmlFilter).firstOrNull()
|
||||
if (file != null) {
|
||||
val obj = GDML.readFile(file.toPath()).toVisual {
|
||||
lUnit = LUnit.CM
|
||||
|
||||
solidConfiguration = { parent, solid ->
|
||||
if (solid.name == "cave") {
|
||||
setProperty(Material3D.MATERIAL_WIREFRAME_KEY, true)
|
||||
}
|
||||
if (parent.physVolumes.isNotEmpty()) {
|
||||
useStyle("opaque") {
|
||||
Material3D.MATERIAL_OPACITY_KEY put 0.3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
canvas.render(obj)
|
||||
}
|
||||
val file = chooseFile("Select a GDML/json file", filters = fileNameFilter).firstOrNull()
|
||||
?: return@action
|
||||
val visual: VisualGroup3D = Visual3D.readFile(file)
|
||||
canvas.render(visual)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -68,8 +52,11 @@ class GDMLView : View() {
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val gdmlFilter = arrayOf(
|
||||
FileChooser.ExtensionFilter("GDML", "*.gdml", "*.xml")
|
||||
private val fileNameFilter = arrayOf(
|
||||
FileChooser.ExtensionFilter("GDML", "*.gdml", "*.xml"),
|
||||
FileChooser.ExtensionFilter("JSON", "*.json"),
|
||||
FileChooser.ExtensionFilter("JSON.ZIP", "*.json.zip"),
|
||||
FileChooser.ExtensionFilter("JSON.GZ", "*.json.gz")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,49 @@
|
||||
package hep.dataforge.vis.spatial.gdml.demo
|
||||
|
||||
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 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 {
|
||||
file.extension == "gdml" || file.extension == "xml" -> {
|
||||
GDML.readFile(file.toPath()).toVisual {
|
||||
lUnit = LUnit.CM
|
||||
|
||||
solidConfiguration = { parent, solid ->
|
||||
if (solid.name == "cave") {
|
||||
setProperty(Material3D.MATERIAL_WIREFRAME_KEY, true)
|
||||
}
|
||||
if (parent.physVolumes.isNotEmpty()) {
|
||||
useStyle("opaque") {
|
||||
Material3D.MATERIAL_OPACITY_KEY put 0.3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
file.extension == "json" -> VisualGroup3D.fromJson(file.readText())
|
||||
file.name.endsWith("json.zip") -> {
|
||||
file.inputStream().use {
|
||||
val unzip = ZipInputStream(it, Charsets.UTF_8)
|
||||
val text = unzip.readAllBytes().decodeToString()
|
||||
VisualGroup3D.fromJson(text)
|
||||
}
|
||||
}
|
||||
file.name.endsWith("json.gz") -> {
|
||||
file.inputStream().use {
|
||||
val unzip = GZIPInputStream(it)
|
||||
val text = unzip.readAllBytes().decodeToString()
|
||||
VisualGroup3D.fromJson(text)
|
||||
}
|
||||
}
|
||||
else -> error("Unknown extension ${file.extension}")
|
||||
}
|
||||
|
||||
fun Visual3D.Companion.readFile(fileName: String): VisualGroup3D = readFile(File(fileName))
|
@ -0,0 +1,19 @@
|
||||
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 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 = Visual3D.json.stringify(VisualGroup3D.serializer(), visual)
|
||||
File("D:\\Work\\Projects\\gdml.kt\\gdml-source\\cubes.json").writeText(json)
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package hep.dataforge.vis.spatial
|
||||
|
||||
import hep.dataforge.names.asName
|
||||
import org.junit.Test
|
||||
import kotlin.test.Ignore
|
||||
|
||||
class FileSerializationTest {
|
||||
@Test
|
||||
@Ignore
|
||||
fun testFileRead(){
|
||||
val text = this::class.java.getResourceAsStream("/cubes.json").readAllBytes().decodeToString()
|
||||
val visual = VisualGroup3D.fromJson(text)
|
||||
visual["composite_001".asName()]
|
||||
}
|
||||
}
|
@ -15,7 +15,7 @@ import hep.dataforge.vis.js.editor.displayPropertyEditor
|
||||
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.Visual3DPlugin
|
||||
import hep.dataforge.vis.spatial.Visual3D
|
||||
import hep.dataforge.vis.spatial.VisualObject3D
|
||||
import hep.dataforge.vis.spatial.VisualObject3D.Companion.VISIBLE_KEY
|
||||
import hep.dataforge.vis.spatial.three.ThreePlugin
|
||||
@ -35,13 +35,13 @@ import org.w3c.dom.HTMLElement
|
||||
import kotlin.browser.document
|
||||
import kotlin.dom.clear
|
||||
|
||||
private class GDMLDemoApp : Application {
|
||||
private class MMDemoApp : Application {
|
||||
|
||||
private val model = Model()
|
||||
|
||||
private val connection = HttpClient {
|
||||
install(JsonFeature) {
|
||||
serializer = KotlinxSerializer(Visual3DPlugin.json)
|
||||
serializer = KotlinxSerializer(Visual3D.json)
|
||||
}
|
||||
}
|
||||
|
||||
@ -122,5 +122,5 @@ private class GDMLDemoApp : Application {
|
||||
}
|
||||
|
||||
fun main() {
|
||||
startApplication(::GDMLDemoApp)
|
||||
startApplication(::MMDemoApp)
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
package ru.mipt.npm.muon.monitor.server
|
||||
|
||||
|
||||
import hep.dataforge.vis.spatial.Visual3DPlugin
|
||||
import hep.dataforge.vis.spatial.Visual3D
|
||||
import io.ktor.application.Application
|
||||
import io.ktor.application.call
|
||||
import io.ktor.application.install
|
||||
@ -35,7 +35,7 @@ fun Application.module() {
|
||||
install(DefaultHeaders)
|
||||
install(CallLogging)
|
||||
install(ContentNegotiation) {
|
||||
serialization(json = Visual3DPlugin.json)
|
||||
serialization(json = Visual3D.json)
|
||||
}
|
||||
install(Routing) {
|
||||
get("/event") {
|
||||
|
@ -7,6 +7,7 @@ import hep.dataforge.meta.number
|
||||
import hep.dataforge.names.plus
|
||||
import hep.dataforge.names.startsWith
|
||||
import hep.dataforge.vis.common.getProperty
|
||||
import hep.dataforge.vis.common.set
|
||||
import hep.dataforge.vis.common.setProperty
|
||||
import hep.dataforge.vis.spatial.*
|
||||
import hep.dataforge.vis.spatial.VisualObject3D.Companion.GEOMETRY_KEY
|
||||
|
BIN
doc/resources/class-diag-3d.png
Normal file
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
BIN
doc/resources/class-diag-common.png
Normal file
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 6.0 KiB |
BIN
doc/resources/gdml-demo.png
Normal file
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 97 KiB |
BIN
doc/resources/muon-monitor.png
Normal file
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
BIN
doc/resources/spatial-showcase.png
Normal file
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |