Merge pull request #2 from mipt-npm/dev

Dev
This commit is contained in:
Peter Klimai 2020-03-07 22:37:47 +03:00 committed by GitHub
commit 64d8b1fca4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
65 changed files with 725 additions and 286 deletions

120
README.md
View File

@ -1,41 +1,99 @@
[![JetBrains Research](https://jb.gg/badges/research.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub) [![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/) ## Table of contents
(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.
The project is developed as a Kotlin multiplatform application, currently * [Introduction](#introduction)
targeting browser JavaScript and JVM. * [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 - 3D visualization of complex experimental set-ups
- Event display such as particle tracks, etc. - Event display such as particle tracks, etc.
- Scales up to few hundred thousands of elements - Scales up to few hundred thousands of elements
- Camera move, rotate, zoom-in and zoom-out - 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 - Settings export and import
- Multiple platform support - 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 ### 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 ### 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 ### 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 ### 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. 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. `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 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/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 To run full-stack app (both server and browser front-end), run
`demo/muon-monitor/application/run` task. `demo/muon-monitor/application/run` task.
##### gdml ##### Example view:
Visualization example for geometry defined as GDML file. Once you open Web application, ![](doc/resources/muon-monitor.png)
drag-and-drop GDML file to the window to see visualization. For example file, use
`demo\gdml\src\jsMain\resources\cubes.gdml`. ### 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)

View File

@ -26,7 +26,7 @@ allprojects {
} }
group = "hep.dataforge" group = "hep.dataforge"
version = "0.1.0-dev" version = "0.1.1-dev"
} }
subprojects{ subprojects{

View File

@ -37,7 +37,7 @@ kotlin {
jsMain{ jsMain{
dependencies { dependencies {
api("hep.dataforge:dataforge-output-html:$dataforgeVersion") api("hep.dataforge:dataforge-output-html:$dataforgeVersion")
api(npm("bootstrap","4.4.1")) //api(npm("bootstrap","4.4.1"))
implementation(npm("jsoneditor")) implementation(npm("jsoneditor"))
implementation(npm("file-saver")) implementation(npm("file-saver"))
} }

View File

@ -1,7 +1,10 @@
package hep.dataforge.vis.common package hep.dataforge.vis.common
import hep.dataforge.meta.MetaItem 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 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 * 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. * 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 { when {
name.isEmpty() -> { name.isEmpty() -> {
if (child != null) { if (child != null) {
@ -100,13 +103,4 @@ abstract class AbstractVisualGroup : AbstractVisualObject(), MutableVisualGroup
structureChangeListeners.forEach { it.callback(name, child) } 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)
}
}
} }

View File

@ -73,6 +73,9 @@ abstract class AbstractVisualObject : VisualObject {
styleCache = it styleCache = it
} }
/**
* All available properties in a layered form
*/
override fun allProperties(): Laminate = Laminate(properties, mergedStyles) override fun allProperties(): Laminate = Laminate(properties, mergedStyles)
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? { override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? {

View File

@ -6,10 +6,11 @@ import hep.dataforge.io.serialization.MetaSerializer
import hep.dataforge.meta.* import hep.dataforge.meta.*
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.names.asName import hep.dataforge.names.asName
import kotlinx.serialization.Serializable import kotlinx.serialization.*
import kotlinx.serialization.Transient
import kotlinx.serialization.UseSerializers
/**
* A container for styles
*/
@Serializable @Serializable
class StyleSheet() { class StyleSheet() {
@Transient @Transient
@ -28,7 +29,7 @@ class StyleSheet() {
} }
/** /**
* Define a style without notifying * Define a style without notifying owner
*/ */
fun define(key: String, style: Meta?) { fun define(key: String, style: Meta?) {
if (style == null) { if (style == null) {
@ -48,6 +49,20 @@ class StyleSheet() {
val newStyle = get(key)?.let { buildMeta(it, builder) } ?: buildMeta(builder) val newStyle = get(key)?.let { buildMeta(it, builder) } ?: buildMeta(builder)
set(key, newStyle.seal()) 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?) { private fun VisualObject.styleChanged(key: String, oldStyle: Meta?, newStyle: Meta?) {

View File

@ -13,7 +13,7 @@ interface VisualFactory<T : VisualObject> {
): T ): T
} }
class VisualPlugin(meta: Meta) : AbstractPlugin(meta) { class Visual(meta: Meta) : AbstractPlugin(meta) {
override val tag: PluginTag get() = Companion.tag 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? 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 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" const val VISUAL_FACTORY_TYPE = "visual.factory"
} }

View File

@ -16,6 +16,10 @@ interface VisualGroup : Provider, Iterable<VisualObject>, VisualObject {
val styleSheet: StyleSheet? val styleSheet: StyleSheet?
/**
* A map of direct children for specific target
* (currently "visual" or "style")
*/
override fun provideTop(target: String): Map<Name, Any> = override fun provideTop(target: String): Map<Name, Any> =
when (target) { when (target) {
VisualObject.TYPE -> children.flatMap { (key, value) -> VisualObject.TYPE -> children.flatMap { (key, value) ->
@ -49,7 +53,7 @@ interface VisualGroup : Provider, Iterable<VisualObject>, VisualObject {
*/ */
fun attachChildren() { fun attachChildren() {
styleSheet?.owner = this styleSheet?.owner = this
this.children.values.forEach { children.values.forEach {
it.parent = this it.parent = this
(it as? VisualGroup)?.attachChildren() (it as? VisualGroup)?.attachChildren()
} }
@ -84,6 +88,10 @@ interface MutableVisualGroup : VisualGroup {
operator fun set(name: Name, child: VisualObject?) 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 } fun MutableVisualGroup.removeAll() = children.keys.map { it.asName() }.forEach { this[it] = null }

View File

@ -2,9 +2,7 @@ package hep.dataforge.vis.common
import hep.dataforge.meta.* import hep.dataforge.meta.*
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.names.NameToken
import hep.dataforge.names.asName import hep.dataforge.names.asName
import hep.dataforge.names.toName
import hep.dataforge.values.Value import hep.dataforge.values.Value
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
import kotlin.properties.ReadOnlyProperty import kotlin.properties.ReadOnlyProperty
@ -58,75 +56,72 @@ class VisualObjectDelegateWrapper<T>(
} }
fun VisualObject.value(default: Value? = null, key: String? = null, inherited: Boolean = false) = fun VisualObject.value(default: Value? = null, name: Name? = null, inherited: Boolean = false) =
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.value } VisualObjectDelegateWrapper(this, name, default, inherited) { it.value }
fun VisualObject.string(default: String? = null, key: String? = null, inherited: Boolean = false) = fun VisualObject.string(default: String? = null, name: Name? = null, inherited: Boolean = false) =
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.string } VisualObjectDelegateWrapper(this, name, default, inherited) { it.string }
fun VisualObject.boolean(default: Boolean? = null, key: String? = null, inherited: Boolean = false) = fun VisualObject.boolean(default: Boolean? = null, name: Name? = null, inherited: Boolean = false) =
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.boolean } VisualObjectDelegateWrapper(this, name, default, inherited) { it.boolean }
fun VisualObject.number(default: Number? = null, key: String? = null, inherited: Boolean = false) = fun VisualObject.number(default: Number? = null, name: Name? = null, inherited: Boolean = false) =
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.number } VisualObjectDelegateWrapper(this, name, default, inherited) { it.number }
fun VisualObject.double(default: Double? = null, key: String? = null, inherited: Boolean = false) = fun VisualObject.double(default: Double? = null, name: Name? = null, inherited: Boolean = false) =
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.double } VisualObjectDelegateWrapper(this, name, default, inherited) { it.double }
fun VisualObject.int(default: Int? = null, key: String? = null, inherited: Boolean = false) = fun VisualObject.int(default: Int? = null, name: Name? = null, inherited: Boolean = false) =
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.int } VisualObjectDelegateWrapper(this, name, default, inherited) { it.int }
fun VisualObject.node(key: String? = null, inherited: Boolean = true) = fun VisualObject.node(name: Name? = null, inherited: Boolean = true) =
VisualObjectDelegateWrapper(this, key?.toName(), null, inherited) { it.node } VisualObjectDelegateWrapper(this, name, null, inherited) { it.node }
fun VisualObject.item(key: String? = null, inherited: Boolean = true) = fun VisualObject.item(name: Name? = null, inherited: Boolean = true) =
VisualObjectDelegateWrapper(this, key?.toName(), null, inherited) { it } VisualObjectDelegateWrapper(this, name, null, inherited) { it }
//fun <T : Configurable> Configurable.spec(spec: Specification<T>, key: String? = null) = ChildConfigDelegate<T>(key) { spec.wrap(this) } //fun <T : Configurable> Configurable.spec(spec: Specification<T>, key: String? = null) = ChildConfigDelegate<T>(key) { spec.wrap(this) }
@JvmName("safeString") @JvmName("safeString")
fun VisualObject.string(default: String, key: String? = null, inherited: Boolean = false) = fun VisualObject.string(default: String, name: Name? = null, inherited: Boolean = false) =
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.string } VisualObjectDelegateWrapper(this, name, default, inherited) { it.string }
@JvmName("safeBoolean") @JvmName("safeBoolean")
fun VisualObject.boolean(default: Boolean, key: String? = null, inherited: Boolean = false) = fun VisualObject.boolean(default: Boolean, name: Name? = null, inherited: Boolean = false) =
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.boolean } VisualObjectDelegateWrapper(this, name, default, inherited) { it.boolean }
@JvmName("safeNumber") @JvmName("safeNumber")
fun VisualObject.number(default: Number, key: String? = null, inherited: Boolean = false) = fun VisualObject.number(default: Number, name: Name? = null, inherited: Boolean = false) =
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.number } VisualObjectDelegateWrapper(this, name, default, inherited) { it.number }
@JvmName("safeDouble") @JvmName("safeDouble")
fun VisualObject.double(default: Double, key: String? = null, inherited: Boolean = false) = fun VisualObject.double(default: Double, name: Name? = null, inherited: Boolean = false) =
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.double } VisualObjectDelegateWrapper(this, name, default, inherited) { it.double }
@JvmName("safeInt") @JvmName("safeInt")
fun VisualObject.int(default: Int, key: String? = null, inherited: Boolean = false) = fun VisualObject.int(default: Int, name: Name? = null, inherited: Boolean = false) =
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.int } VisualObjectDelegateWrapper(this, name, default, inherited) { it.int }
inline fun <reified E : Enum<E>> VisualObject.enum(default: E, key: String? = null, inherited: Boolean = false) = inline fun <reified E : Enum<E>> VisualObject.enum(default: E, name: Name? = null, inherited: Boolean = false) =
VisualObjectDelegateWrapper( VisualObjectDelegateWrapper(this, name, default, inherited) {
this, item -> item.string?.let { enumValueOf<E>(it) }
key?.let { NameToken(it).asName() }, }
default,
inherited
) { item -> item.string?.let { enumValueOf<E>(it) } }
//merge properties //merge properties
fun <T> VisualObject.merge( fun <T> VisualObject.merge(
key: String? = null, name: Name? = null,
transformer: (Sequence<MetaItem<*>>) -> T transformer: (Sequence<MetaItem<*>>) -> T
): ReadOnlyProperty<VisualObject, T> { ): ReadOnlyProperty<VisualObject, T> {
return object : ReadOnlyProperty<Any?, T> { return object : ReadOnlyProperty<Any?, T> {
override fun getValue(thisRef: Any?, property: KProperty<*>): 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<*>> { val sequence = sequence<MetaItem<*>> {
var thisObj: VisualObject? = this@merge var thisObj: VisualObject? = this@merge
while (thisObj != null) { while (thisObj != null) {
thisObj.config[name]?.let { yield(it) } thisObj.config[actualName]?.let { yield(it) }
thisObj = thisObj.parent thisObj = thisObj.parent
} }
} }

View File

@ -1,8 +1,7 @@
package hep.dataforge.vis.js.editor package hep.dataforge.vis.js.editor
import kotlinx.html.TagConsumer import kotlinx.html.*
import kotlinx.html.js.div import kotlinx.html.js.div
import kotlinx.html.js.h3
import org.w3c.dom.HTMLElement import org.w3c.dom.HTMLElement
inline fun TagConsumer<HTMLElement>.card(title: String, crossinline block: TagConsumer<HTMLElement>.() -> Unit) { 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)
}

View File

@ -26,12 +26,11 @@ fun Element.displayObjectTree(
} }
private fun TagConsumer<HTMLElement>.subTree( private fun TagConsumer<HTMLElement>.subTree(
fullName: Name, name: Name,
obj: VisualObject, obj: VisualObject,
clickCallback: (Name) -> Unit clickCallback: (Name) -> Unit
) { ) {
// val fullName = parentName + token val token = name.last()?.toString()?:"World"
val token = fullName.last()?.toString()?:"World"
//display as node if any child is visible //display as node if any child is visible
if (obj is VisualGroup && obj.children.keys.any { !it.body.startsWith("@") }) { if (obj is VisualGroup && obj.children.keys.any { !it.body.startsWith("@") }) {
@ -40,7 +39,7 @@ private fun TagConsumer<HTMLElement>.subTree(
toggle = span("objTree-caret") toggle = span("objTree-caret")
label("objTree-label") { label("objTree-label") {
+token +token
onClickFunction = { clickCallback(fullName) } onClickFunction = { clickCallback(name) }
} }
} }
val subtree = ul("objTree-subtree") val subtree = ul("objTree-subtree")
@ -55,7 +54,7 @@ private fun TagConsumer<HTMLElement>.subTree(
.forEach { (childToken, child) -> .forEach { (childToken, child) ->
append { append {
li().apply { li().apply {
subTree(fullName + childToken, child, clickCallback) subTree(name + childToken, child, clickCallback)
} }
} }
} }
@ -70,7 +69,7 @@ private fun TagConsumer<HTMLElement>.subTree(
span("objTree-leaf") span("objTree-leaf")
label("objTree-label") { label("objTree-label") {
+token +token
onClickFunction = { clickCallback(fullName) } onClickFunction = { clickCallback(name) }
} }
} }
} }

View File

@ -7,7 +7,7 @@ kotlin {
val commonMain by getting { val commonMain by getting {
dependencies { dependencies {
api(project(":dataforge-vis-spatial")) api(project(":dataforge-vis-spatial"))
api("scientifik:gdml:0.1.4") api("scientifik:gdml:0.1.6")
} }
} }
} }

View File

@ -5,6 +5,7 @@ import hep.dataforge.names.Name
import hep.dataforge.names.asName import hep.dataforge.names.asName
import hep.dataforge.names.plus import hep.dataforge.names.plus
import hep.dataforge.vis.common.get import hep.dataforge.vis.common.get
import hep.dataforge.vis.common.set
import hep.dataforge.vis.spatial.* import hep.dataforge.vis.spatial.*
import hep.dataforge.vis.spatial.World.ONE import hep.dataforge.vis.spatial.World.ONE
import hep.dataforge.vis.spatial.World.ZERO import hep.dataforge.vis.spatial.World.ZERO
@ -66,6 +67,12 @@ private fun VisualGroup3D.addSolid(
solid.deltaphi * aScale, solid.deltaphi * aScale,
name 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) { is GDMLXtru -> extrude(name) {
shape { shape {
solid.vertices.forEach { solid.vertices.forEach {
@ -171,7 +178,7 @@ private fun VisualGroup3D.addPhysicalVolume(
context.proto[fullName] = volume(context, volume) context.proto[fullName] = volume(context, volume)
} }
this[physVolume.name ?: ""] = Proxy(fullName).apply { this[physVolume.name ?: ""] = Proxy(this, fullName).apply {
withPosition( withPosition(
context.lUnit, context.lUnit,
physVolume.resolvePosition(context.root), physVolume.resolvePosition(context.root),

View File

@ -135,7 +135,7 @@ private fun jsonSchema(descriptor: SerialDescriptor, context: SerialModule): Jso
} }
fun main() { fun main() {
val context = Visual3DPlugin.serialModule val context = Visual3D.serialModule
val definitions = json { val definitions = json {
"children" to json { "children" to json {
"anyOf" to jsonArray { "anyOf" to jsonArray {

View File

@ -8,7 +8,7 @@ import java.nio.file.Path
fun GDML.Companion.readFile(file: Path): GDML { fun GDML.Companion.readFile(file: Path): GDML {
val xmlReader = StAXReader(Files.newInputStream(file), "UTF-8") 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 = {}) { fun VisualGroup3D.gdml(file: Path, key: String = "", transformer: GDMLTransformer.() -> Unit = {}) {

View File

@ -20,7 +20,7 @@ kotlin {
} }
jvmMain { jvmMain {
dependencies { dependencies {
implementation("org.fxyz3d:fxyz3d:0.5.2") { api("org.fxyz3d:fxyz3d:0.5.2") {
exclude(module = "slf4j-simple") exclude(module = "slf4j-simple")
} }
api("org.jetbrains.kotlinx:kotlinx-coroutines-javafx:${Scientifik.coroutinesVersion}") api("org.jetbrains.kotlinx:kotlinx-coroutines-javafx:${Scientifik.coroutinesVersion}")

View File

@ -10,13 +10,15 @@ import hep.dataforge.meta.get
import hep.dataforge.vis.common.AbstractVisualObject import hep.dataforge.vis.common.AbstractVisualObject
import hep.dataforge.vis.common.VisualFactory import hep.dataforge.vis.common.VisualFactory
import hep.dataforge.vis.common.VisualObject 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.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers import kotlinx.serialization.UseSerializers
import kotlin.reflect.KClass import kotlin.reflect.KClass
@Serializable @Serializable
@SerialName("3d.box") @SerialName(TYPE_NAME)
class Box( class Box(
val xSize: Float, val xSize: Float,
val ySize: Float, val ySize: Float,
@ -52,7 +54,8 @@ class Box(
} }
companion object : VisualFactory<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 override val type: KClass<Box> get() = Box::class

View File

@ -6,6 +6,8 @@ import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.meta.Config import hep.dataforge.meta.Config
import hep.dataforge.meta.update import hep.dataforge.meta.update
import hep.dataforge.vis.common.AbstractVisualObject import hep.dataforge.vis.common.AbstractVisualObject
import hep.dataforge.vis.common.set
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers import kotlinx.serialization.UseSerializers
@ -16,6 +18,7 @@ enum class CompositeType {
} }
@Serializable @Serializable
@SerialName("3d.composite")
class Composite( class Composite(
val compositeType: CompositeType, val compositeType: CompositeType,
val first: VisualObject3D, val first: VisualObject3D,

View File

@ -5,6 +5,8 @@ package hep.dataforge.vis.spatial
import hep.dataforge.io.serialization.ConfigSerializer import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.meta.Config import hep.dataforge.meta.Config
import hep.dataforge.vis.common.AbstractVisualObject import hep.dataforge.vis.common.AbstractVisualObject
import hep.dataforge.vis.common.set
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers import kotlinx.serialization.UseSerializers
import kotlin.math.cos import kotlin.math.cos
@ -14,6 +16,7 @@ import kotlin.math.sin
* A cylinder or cut cone segment * A cylinder or cut cone segment
*/ */
@Serializable @Serializable
@SerialName("3d.cone")
class ConeSegment( class ConeSegment(
var radius: Float, var radius: Float,
var height: Float, var height: Float,
@ -83,3 +86,16 @@ inline fun VisualGroup3D.cylinder(
height.toFloat(), height.toFloat(),
r.toFloat() r.toFloat()
).apply(block).also { set(name, it) } ).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) }

View File

@ -5,10 +5,13 @@ package hep.dataforge.vis.spatial
import hep.dataforge.io.serialization.ConfigSerializer import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.meta.Config import hep.dataforge.meta.Config
import hep.dataforge.vis.common.AbstractVisualObject import hep.dataforge.vis.common.AbstractVisualObject
import hep.dataforge.vis.common.set
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers import kotlinx.serialization.UseSerializers
@Serializable @Serializable
@SerialName("3d.convex")
class Convex(val points: List<Point3D>) : AbstractVisualObject(), VisualObject3D { class Convex(val points: List<Point3D>) : AbstractVisualObject(), VisualObject3D {
@Serializable(ConfigSerializer::class) @Serializable(ConfigSerializer::class)

View File

@ -4,6 +4,8 @@ package hep.dataforge.vis.spatial
import hep.dataforge.io.serialization.ConfigSerializer import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.meta.Config import hep.dataforge.meta.Config
import hep.dataforge.vis.common.AbstractVisualObject import hep.dataforge.vis.common.AbstractVisualObject
import hep.dataforge.vis.common.set
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers import kotlinx.serialization.UseSerializers
import kotlin.math.PI 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) data class Layer(var x: Float, var y: Float, var z: Float, var scale: Float)
@Serializable @Serializable
@SerialName("3d.extrude")
class Extruded( class Extruded(
var shape: List<Point2D> = ArrayList(), var shape: List<Point2D> = ArrayList(),
var layers: MutableList<Layer> = ArrayList() var layers: MutableList<Layer> = ArrayList()

View File

@ -31,6 +31,9 @@ fun GeometryBuilder<*>.face4(
face(vertex1, vertex3, vertex4, normal, meta) face(vertex1, vertex3, vertex4, normal, meta)
} }
/**
* [Shape] is a [VisualObject3D] that can represent its own geometry as a set of polygons.
*/
interface Shape : VisualObject3D { interface Shape : VisualObject3D {
fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>) fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>)
} }

View File

@ -5,10 +5,13 @@ package hep.dataforge.vis.spatial
import hep.dataforge.io.serialization.ConfigSerializer import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.meta.Config import hep.dataforge.meta.Config
import hep.dataforge.vis.common.AbstractVisualObject import hep.dataforge.vis.common.AbstractVisualObject
import hep.dataforge.vis.common.set
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers import kotlinx.serialization.UseSerializers
@Serializable @Serializable
@SerialName("3d.label")
class Label3D(var text: String, var fontSize: Double, var fontFamily: String) : AbstractVisualObject(), class Label3D(var text: String, var fontSize: Double, var fontFamily: String) : AbstractVisualObject(),
VisualObject3D { VisualObject3D {
@Serializable(ConfigSerializer::class) @Serializable(ConfigSerializer::class)

View File

@ -7,16 +7,29 @@ import hep.dataforge.names.plus
import hep.dataforge.values.ValueType import hep.dataforge.values.ValueType
import hep.dataforge.vis.common.Colors import hep.dataforge.vis.common.Colors
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_COLOR_KEY 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 import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_OPACITY_KEY
class Material3D(override val config: Config) : Specific { class Material3D(override val config: Config) : Specific {
/**
* Primary web-color for the material
*/
var color by string(key = COLOR_KEY) 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) var opacity by float(1f, key = OPACITY_KEY)
/**
* Replace material by wire frame
*/
var wireframe by boolean(false, WIREFRAME_KEY) var wireframe by boolean(false, WIREFRAME_KEY)
companion object : Specification<Material3D> { companion object : Specification<Material3D> {
@ -25,7 +38,7 @@ class Material3D(override val config: Config) : Specific {
val MATERIAL_KEY = "material".asName() val MATERIAL_KEY = "material".asName()
internal val COLOR_KEY = "color".asName() internal val COLOR_KEY = "color".asName()
val MATERIAL_COLOR_KEY = MATERIAL_KEY + COLOR_KEY 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() internal val OPACITY_KEY = "opacity".asName()
val MATERIAL_OPACITY_KEY = MATERIAL_KEY + OPACITY_KEY val MATERIAL_OPACITY_KEY = MATERIAL_KEY + OPACITY_KEY
internal val WIREFRAME_KEY = "wireframe".asName() 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) { fun VisualObject3D.color(rgb: Int) {
setProperty(MATERIAL_COLOR_KEY, rgb) setProperty(MATERIAL_COLOR_KEY, rgb)
} }
@ -76,16 +95,15 @@ var VisualObject3D.color: String?
setProperty(MATERIAL_COLOR_KEY, value) setProperty(MATERIAL_COLOR_KEY, value)
} }
//var VisualObject3D.material: Material3D? val VisualObject3D.material: Material3D?
// get() = getProperty(MATERIAL_KEY).node?.let { Material3D.wrap(it) } get() = getProperty(MATERIAL_KEY).node?.let { Material3D.wrap(it) }
// set(value) = setProperty(MATERIAL_KEY, value?.config)
fun VisualObject3D.material(builder: Material3D.() -> Unit) { fun VisualObject3D.material(builder: Material3D.() -> Unit) {
val node = config[Material3D.MATERIAL_KEY].node val node = config[MATERIAL_KEY].node
if (node != null) { if (node != null) {
Material3D.update(node, builder) Material3D.update(node, builder)
} else { } else {
config[Material3D.MATERIAL_KEY] = Material3D(builder) config[MATERIAL_KEY] = Material3D(builder)
} }
} }

View File

@ -4,12 +4,17 @@ package hep.dataforge.vis.spatial
import hep.dataforge.io.serialization.ConfigSerializer import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.meta.Config 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.AbstractVisualObject
import hep.dataforge.vis.common.number import hep.dataforge.vis.common.set
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers import kotlinx.serialization.UseSerializers
@Serializable @Serializable
@SerialName("3d.line")
class PolyLine(var points: List<Point3D>) : AbstractVisualObject(), VisualObject3D { class PolyLine(var points: List<Point3D>) : AbstractVisualObject(), VisualObject3D {
@Serializable(ConfigSerializer::class) @Serializable(ConfigSerializer::class)
override var properties: Config? = null override var properties: Config? = null
@ -19,7 +24,11 @@ class PolyLine(var points: List<Point3D>) : AbstractVisualObject(), VisualObject
override var scale: Point3D? = null override var scale: Point3D? = null
//var lineType by string() //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()
}
} }

View File

@ -26,7 +26,11 @@ import kotlin.collections.set
*/ */
@Serializable @Serializable
@SerialName("3d.proxy") @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 position: Point3D? = null
override var rotation: 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> override val children: Map<NameToken, ProxyChild>
get() = (prototype as? MutableVisualGroup)?.children get() = (prototype as? VisualGroup)?.children
?.filter { !it.key.toString().startsWith("@") } ?.filter { !it.key.toString().startsWith("@") }
?.mapValues { ?.mapValues {
ProxyChild(it.key.asName()) 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 allProperties(): Laminate = Laminate(properties, mergedStyles, prototype.allProperties())
override fun attachChildren() {
//do nothing
}
//override fun findAllStyles(): Laminate = Laminate((styles + prototype.styles).mapNotNull { findStyle(it) }) //override fun findAllStyles(): Laminate = Laminate((styles + prototype.styles).mapNotNull { findStyle(it) })
@Serializable
inner class ProxyChild(val name: Name) : AbstractVisualObject(), VisualGroup { inner class ProxyChild(val name: Name) : AbstractVisualObject(), VisualGroup {
val prototype: VisualObject get() = prototypeFor(name) 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()) override fun allProperties(): Laminate = Laminate(properties, mergedStyles, prototype.allProperties())
@ -149,7 +159,7 @@ inline fun VisualGroup3D.ref(
templateName: Name, templateName: Name,
name: String = "", name: String = "",
block: Proxy.() -> Unit = {} 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 * Add new proxy wrapping given object and automatically adding it to the prototypes
@ -162,7 +172,9 @@ fun VisualGroup3D.proxy(
): Proxy { ): Proxy {
val existing = getPrototype(templateName) val existing = getPrototype(templateName)
if (existing == null) { if (existing == null) {
setPrototype(templateName, obj) prototypes {
this[templateName] = obj
}
} else if (existing != obj) { } else if (existing != obj) {
error("Can't add different prototype on top of existing one") error("Can't add different prototype on top of existing one")
} }

View File

@ -5,6 +5,8 @@ package hep.dataforge.vis.spatial
import hep.dataforge.io.serialization.ConfigSerializer import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.meta.Config import hep.dataforge.meta.Config
import hep.dataforge.vis.common.AbstractVisualObject import hep.dataforge.vis.common.AbstractVisualObject
import hep.dataforge.vis.common.set
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers import kotlinx.serialization.UseSerializers
import kotlin.math.PI import kotlin.math.PI
@ -12,6 +14,7 @@ import kotlin.math.cos
import kotlin.math.sin import kotlin.math.sin
@Serializable @Serializable
@SerialName("3d.sphere")
class Sphere( class Sphere(
var radius: Float, var radius: Float,
var phiStart: Float = 0f, var phiStart: Float = 0f,

View File

@ -4,6 +4,8 @@ package hep.dataforge.vis.spatial
import hep.dataforge.io.serialization.ConfigSerializer import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.meta.Config import hep.dataforge.meta.Config
import hep.dataforge.vis.common.AbstractVisualObject import hep.dataforge.vis.common.AbstractVisualObject
import hep.dataforge.vis.common.set
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers import kotlinx.serialization.UseSerializers
import kotlin.math.PI import kotlin.math.PI
@ -14,6 +16,7 @@ import kotlin.math.sin
* Straight tube segment * Straight tube segment
*/ */
@Serializable @Serializable
@SerialName("3d.tube")
class Tube( class Tube(
var radius: Float, var radius: Float,
var height: Float, var height: Float,

View File

@ -9,29 +9,32 @@ import hep.dataforge.io.serialization.MetaSerializer
import hep.dataforge.io.serialization.NameSerializer import hep.dataforge.io.serialization.NameSerializer
import hep.dataforge.meta.* import hep.dataforge.meta.*
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.names.toName
import hep.dataforge.vis.common.Visual
import hep.dataforge.vis.common.VisualObject import hep.dataforge.vis.common.VisualObject
import hep.dataforge.vis.common.VisualPlugin
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration import kotlinx.serialization.json.JsonConfiguration
import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.contextual import kotlinx.serialization.modules.contextual
import kotlin.reflect.KClass 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 val tag: PluginTag get() = Companion.tag
override fun provideTop(target: String): Map<Name, Any> { override fun provideTop(target: String): Map<Name, Any> = if (target == Visual.VISUAL_FACTORY_TYPE) {
return if (target == VisualPlugin.VISUAL_FACTORY_TYPE) { mapOf(Box.TYPE_NAME.toName() to Box)
mapOf()
} else { } else {
emptyMap() 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 tag: PluginTag = PluginTag(name = "visual.spatial", group = PluginTag.DATAFORGE_GROUP)
override val type: KClass<out Visual3DPlugin> = Visual3DPlugin::class override val type: KClass<out Visual3D> = Visual3D::class
override fun invoke(meta: Meta, context: Context): Visual3DPlugin = Visual3DPlugin(meta) override fun invoke(meta: Meta, context: Context): Visual3D = Visual3D(meta)
val serialModule = SerializersModule { val serialModule = SerializersModule {
contextual(Point3DSerializer) contextual(Point3DSerializer)

View File

@ -19,6 +19,7 @@ import hep.dataforge.names.isEmpty
import hep.dataforge.vis.common.AbstractVisualGroup import hep.dataforge.vis.common.AbstractVisualGroup
import hep.dataforge.vis.common.StyleSheet import hep.dataforge.vis.common.StyleSheet
import hep.dataforge.vis.common.VisualObject import hep.dataforge.vis.common.VisualObject
import hep.dataforge.vis.common.set
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers import kotlinx.serialization.UseSerializers
@ -30,19 +31,19 @@ import kotlin.collections.set
@Serializable @Serializable
@SerialName("group.3d") @SerialName("group.3d")
class VisualGroup3D : AbstractVisualGroup(), VisualObject3D { class VisualGroup3D : AbstractVisualGroup(), VisualObject3D {
override var styleSheet: StyleSheet? = null
private set
/** /**
* A container for templates visible inside this group * A container for templates visible inside this group
*/ */
@SerialName(PROTOTYPES_KEY)
var prototypes: VisualGroup3D? = null var prototypes: VisualGroup3D? = null
set(value) { set(value) {
value?.parent = this value?.parent = this
field = value 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 //FIXME to be lifted to AbstractVisualGroup after https://github.com/Kotlin/kotlinx.serialization/issues/378 is fixed
override var properties: Config? = null override var properties: Config? = null
@ -54,28 +55,34 @@ class VisualGroup3D : AbstractVisualGroup(), VisualObject3D {
private val _children = HashMap<NameToken, VisualObject>() private val _children = HashMap<NameToken, VisualObject>()
override val children: Map<NameToken, VisualObject> get() = _children override val children: Map<NameToken, VisualObject> get() = _children
init { // init {
//Do after deserialization // //Do after deserialization
attachChildren() // attachChildren()
// }
override fun attachChildren() {
prototypes?.parent = this
prototypes?.attachChildren()
super.attachChildren()
} }
/** /**
* Update or create stylesheet * Update or create stylesheet
*/ */
fun styleSheet(block: StyleSheet.() -> Unit) { 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() res.block()
} }
override fun removeChild(token: NameToken) { override fun removeChild(token: NameToken) {
_children.remove(token) _children.remove(token)?.run { parent = null }
childrenChanged(token.asName(), null) childrenChanged(token.asName(), null)
} }
override fun setChild(token: NameToken, child: VisualObject) { override fun setChild(token: NameToken, child: VisualObject) {
if (child.parent == null) { if (child.parent == null) {
child.parent = this child.parent = this
} else { } else if (child.parent !== this) {
error("Can't reassign existing parent for $child") error("Can't reassign existing parent for $child")
} }
_children[token] = child _children[token] = child
@ -102,42 +109,32 @@ class VisualGroup3D : AbstractVisualGroup(), VisualObject3D {
} }
} }
override fun attachChildren() {
super.attachChildren()
prototypes?.run {
parent = this
attachChildren()
}
}
companion object { 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 * 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) 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) { inline fun VisualGroup3D.prototypes(builder: VisualGroup3D.() -> Unit): Unit {
(prototypes ?: VisualGroup3D().also { this.prototypes = it })[name] = obj (prototypes ?: VisualGroup3D().also { prototypes = it }).run(builder)
} }
/** /**
* Define a group with given [key], attach it to this parent and return it. * Define a group with given [key], attach it to this parent and return it.
*/ */
fun VisualGroup3D.group(key: String = "", action: VisualGroup3D.() -> Unit = {}): VisualGroup3D = fun VisualGroup3D.group(key: String = "", action: VisualGroup3D.() -> Unit = {}): VisualGroup3D =
VisualGroup3D().apply(action).also { set(key, it) } 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)
} }

View File

@ -22,22 +22,10 @@ interface VisualObject3D : VisualObject {
var rotation: Point3D? var rotation: Point3D?
var scale: 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 { companion object {
val VISIBLE_KEY = "visible".asName() val VISIBLE_KEY = "visible".asName()
val SELECTED_KEY = "selected".asName() // val SELECTED_KEY = "selected".asName()
val DETAIL_KEY = "detail".asName() val DETAIL_KEY = "detail".asName()
val LAYER_KEY = "layer".asName() val LAYER_KEY = "layer".asName()
val IGNORE_KEY = "ignore".asName() val IGNORE_KEY = "ignore".asName()

View File

@ -46,8 +46,8 @@ object Point3DSerializer : KSerializer<Point3D> {
when (val i = decodeElementIndex(descriptor)) { when (val i = decodeElementIndex(descriptor)) {
CompositeDecoder.READ_DONE -> break@loop CompositeDecoder.READ_DONE -> break@loop
0 -> x = decodeNullableSerializableElement(descriptor, 0, DoubleSerializer.nullable) ?: 0.0 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
2 -> z = decodeNullableSerializableElement(descriptor, 0, DoubleSerializer.nullable) ?: 0.0 2 -> z = decodeNullableSerializableElement(descriptor, 2, DoubleSerializer.nullable) ?: 0.0
else -> throw SerializationException("Unknown index $i") else -> throw SerializationException("Unknown index $i")
} }
} }
@ -79,7 +79,7 @@ object Point2DSerializer : KSerializer<Point2D> {
when (val i = decodeElementIndex(descriptor)) { when (val i = decodeElementIndex(descriptor)) {
CompositeDecoder.READ_DONE -> break@loop CompositeDecoder.READ_DONE -> break@loop
0 -> x = decodeNullableSerializableElement(descriptor, 0, DoubleSerializer.nullable) ?: 0.0 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") else -> throw SerializationException("Unknown index $i")
} }
} }

View File

@ -51,7 +51,9 @@ object RemoveSingleChild : VisualTreeTransform<VisualGroup3D>() {
} }
replaceChildren() replaceChildren()
prototypes?.replaceChildren() prototypes {
replaceChildren()
}
} }
override fun VisualGroup3D.clone(): VisualGroup3D { override fun VisualGroup3D.clone(): VisualGroup3D {

View File

@ -6,6 +6,7 @@ import hep.dataforge.vis.common.MutableVisualGroup
import hep.dataforge.vis.common.VisualGroup import hep.dataforge.vis.common.VisualGroup
import hep.dataforge.vis.spatial.Proxy import hep.dataforge.vis.spatial.Proxy
import hep.dataforge.vis.spatial.VisualGroup3D import hep.dataforge.vis.spatial.VisualGroup3D
import hep.dataforge.vis.spatial.prototypes
object UnRef : VisualTreeTransform<VisualGroup3D>() { object UnRef : VisualTreeTransform<VisualGroup3D>() {
private fun VisualGroup.countRefs(): Map<Name, Int> { private fun VisualGroup.countRefs(): Map<Name, Int> {

View File

@ -25,7 +25,7 @@ class ConvexTest {
val convex = group.first() as Convex 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 meta = json.toMeta()
val points = meta.getIndexed("points").values.map { (it as MetaItem.NodeItem<*>).node.point3D()} val points = meta.getIndexed("points").values.map { (it as MetaItem.NodeItem<*>).node.point3D()}

View File

@ -1,6 +1,6 @@
package hep.dataforge.vis.spatial 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 kotlinx.serialization.ImplicitReflectionSerializer
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals

View File

@ -31,13 +31,15 @@ interface ThreeFactory<in T : VisualObject> {
/** /**
* Update position, rotation and visibility * Update position, rotation and visibility
*/ */
fun Object3D.updatePosition(obj: VisualObject3D) { fun Object3D.updatePosition(obj: VisualObject) {
visible = obj.visible ?: true visible = obj.visible ?: true
if(obj is VisualObject3D) {
position.set(obj.x, obj.y, obj.z) position.set(obj.x, obj.y, obj.z)
setRotationFromEuler(obj.euler) setRotationFromEuler(obj.euler)
scale.set(obj.scaleX, obj.scaleY, obj.scaleZ) scale.set(obj.scaleX, obj.scaleY, obj.scaleZ)
updateMatrix() updateMatrix()
} }
}
///** ///**
// * Unsafe invocation of a factory // * Unsafe invocation of a factory
@ -54,7 +56,7 @@ fun Object3D.updatePosition(obj: VisualObject3D) {
/** /**
* Update non-position non-geometry property * 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)) { if (this is Mesh && propertyName.startsWith(MATERIAL_KEY)) {
this.material = getMaterial(source) this.material = getMaterial(source)
} else if ( } else if (

View File

@ -25,7 +25,7 @@ object ThreeLabelFactory : ThreeFactory<Label3D> {
} ) } )
return Mesh(textGeo, getMaterial(obj)).apply { return Mesh(textGeo, getMaterial(obj)).apply {
updatePosition(obj) updatePosition(obj)
obj.onPropertyChange(this@ThreeLabelFactory){name: Name, before: MetaItem<*>?, after: MetaItem<*>? -> obj.onPropertyChange(this@ThreeLabelFactory){ _: Name, _: MetaItem<*>?, _: MetaItem<*>? ->
//TODO //TODO
} }
} }

View File

@ -18,8 +18,7 @@ object ThreeLineFactory : ThreeFactory<PolyLine> {
vertices = obj.points.toTypedArray() vertices = obj.points.toTypedArray()
} }
val material = val material = ThreeMaterials.getLineMaterial(obj.getProperty(MeshThreeFactory.EDGES_MATERIAL_KEY).node)
ThreeMaterials.getLineMaterial(obj.getProperty(MeshThreeFactory.EDGES_MATERIAL_KEY).node)
material.linewidth = obj.thickness.toDouble() 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

View File

@ -3,8 +3,8 @@ package hep.dataforge.vis.spatial.three
import hep.dataforge.meta.* import hep.dataforge.meta.*
import hep.dataforge.values.ValueType import hep.dataforge.values.ValueType
import hep.dataforge.vis.common.Colors import hep.dataforge.vis.common.Colors
import hep.dataforge.vis.common.VisualObject
import hep.dataforge.vis.spatial.Material3D import hep.dataforge.vis.spatial.Material3D
import hep.dataforge.vis.spatial.VisualObject3D
import info.laht.threekt.materials.LineBasicMaterial import info.laht.threekt.materials.LineBasicMaterial
import info.laht.threekt.materials.Material import info.laht.threekt.materials.Material
import info.laht.threekt.materials.MeshBasicMaterial 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 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 { MeshPhongMaterial().apply {
color = meta[Material3D.COLOR_KEY]?.getColor() ?: DEFAULT_COLOR 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 opacity = meta[Material3D.OPACITY_KEY]?.double ?: 1.0
transparent = opacity < 1.0 transparent = opacity < 1.0
wireframe = meta[Material3D.WIREFRAME_KEY].boolean ?: false wireframe = meta[Material3D.WIREFRAME_KEY].boolean ?: false

View File

@ -74,8 +74,8 @@ class ThreePlugin : AbstractPlugin() {
return@onChildrenChange return@onChildrenChange
} }
val parentName = name.cutLast() // val parentName = name.cutLast()
val childName = name.last()!! // val childName = name.last()!!
//removing old object //removing old object
findChild(name)?.let { oldChild -> findChild(name)?.let { oldChild ->

View File

@ -25,7 +25,7 @@ class ThreeProxyFactory(val three: ThreePlugin) : ThreeFactory<Proxy> {
if (name.first()?.body == PROXY_CHILD_PROPERTY_PREFIX) { if (name.first()?.body == PROXY_CHILD_PROPERTY_PREFIX) {
val childName = name.first()?.index?.toName() ?: error("Wrong syntax for proxy child property: '$name'") val childName = name.first()?.index?.toName() ?: error("Wrong syntax for proxy child property: '$name'")
val propertyName = name.cutFirst() 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") val child = object3D.findChild(childName)?: error("Object child with name '$childName' not found")
child.updateProperty(proxyChild, propertyName) child.updateProperty(proxyChild, propertyName)
} else { } else {

View File

@ -1,8 +1,8 @@
package hep.dataforge.vis.spatial.three package hep.dataforge.vis.spatial.three
import hep.dataforge.js.requireJS import hep.dataforge.js.requireJS
import hep.dataforge.vis.js.editor.card import hep.dataforge.vis.js.editor.accordion
import hep.dataforge.vis.spatial.Visual3DPlugin import hep.dataforge.vis.spatial.Visual3D
import hep.dataforge.vis.spatial.VisualGroup3D import hep.dataforge.vis.spatial.VisualGroup3D
import kotlinx.html.InputType import kotlinx.html.InputType
import kotlinx.html.TagConsumer import kotlinx.html.TagConsumer
@ -28,7 +28,8 @@ private fun saveData(event: Event, fileName: String, mimeType: String = "text/pl
fun Element.displayCanvasControls(canvas: ThreeCanvas, block: TagConsumer<HTMLElement>.() -> Unit = {}) { fun Element.displayCanvasControls(canvas: ThreeCanvas, block: TagConsumer<HTMLElement>.() -> Unit = {}) {
clear() clear()
append { append {
card("Settings") { accordion("controls") {
entry("Settings") {
div("row") { div("row") {
div("col-2") { div("col-2") {
label("checkbox-inline") { label("checkbox-inline") {
@ -46,7 +47,63 @@ fun Element.displayCanvasControls(canvas: ThreeCanvas, block: TagConsumer<HTMLEl
+"Export" +"Export"
onClickFunction = { onClickFunction = {
val json = (canvas.content as? VisualGroup3D)?.let { group -> val json = (canvas.content as? VisualGroup3D)?.let { group ->
Visual3DPlugin.json.stringify( 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") {
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(), VisualGroup3D.serializer(),
group group
) )
@ -81,7 +138,7 @@ fun Element.displayCanvasControls(canvas: ThreeCanvas, block: TagConsumer<HTMLEl
} }
} }
} }
} }*/
block() block()
} }
} }

View File

@ -66,7 +66,7 @@ fun MetaItem<*>?.material(): Material {
is MetaItem.NodeItem -> PhongMaterial().apply { is MetaItem.NodeItem -> PhongMaterial().apply {
val opacity = node[Material3D.OPACITY_KEY].double ?: 1.0 val opacity = node[Material3D.OPACITY_KEY].double ?: 1.0
diffuseColor = node[Material3D.COLOR_KEY]?.color(opacity) ?: Color.DARKGREY 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
} }
} }
} }

View File

@ -13,8 +13,8 @@ class FXProxyFactory(val plugin: FX3DPlugin) : FX3DFactory<Proxy> {
override val type: KClass<in Proxy> get() = Proxy::class override val type: KClass<in Proxy> get() = Proxy::class
override fun invoke(obj: Proxy, binding: VisualObjectFXBinding): Node { override fun invoke(obj: Proxy, binding: VisualObjectFXBinding): Node {
val template = obj.prototype val prototype = obj.prototype
val node = plugin.buildNode(template) val node = plugin.buildNode(prototype)
obj.onPropertyChange(this) { name, _, _ -> obj.onPropertyChange(this) { name, _, _ ->
if (name.first()?.body == Proxy.PROXY_CHILD_PROPERTY_PREFIX) { if (name.first()?.body == Proxy.PROXY_CHILD_PROPERTY_PREFIX) {

View File

@ -1,12 +1,13 @@
package hep.dataforge.vis.spatial 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 data class Point2D(actual var x: Double, actual var y: Double) {
actual constructor(x: Number, y: Number) : this(x.toDouble(), y.toDouble()) 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( actual constructor(x: Number, y: Number, z: Number) : this(
org.fxyz3d.geometry.Point3D( FXPoint3D(
x.toFloat(), x.toFloat(),
y.toFloat(), y.toFloat(),
z.toFloat() z.toFloat()
@ -32,7 +33,7 @@ actual class Point3D(val point: org.fxyz3d.geometry.Point3D) {
} }
override fun equals(other: Any?): Boolean { 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 { override fun hashCode(): Int {

View File

@ -27,6 +27,11 @@ kotlin {
api(project(":dataforge-vis-spatial-gdml")) api(project(":dataforge-vis-spatial-gdml"))
} }
} }
jvmMain{
dependencies {
api("org.fxyz3d:fxyz3d:0.5.2")
}
}
} }
} }

View File

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

View File

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

View File

@ -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_COLOR_KEY
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_OPACITY_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.Material3D.Companion.MATERIAL_WIREFRAME_KEY
import hep.dataforge.vis.spatial.Visual3DPlugin
import hep.dataforge.vis.spatial.VisualGroup3D import hep.dataforge.vis.spatial.VisualGroup3D
import hep.dataforge.vis.spatial.VisualObject3D import hep.dataforge.vis.spatial.VisualObject3D
import hep.dataforge.vis.spatial.VisualObject3D.Companion.VISIBLE_KEY 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.displayCanvasControls
import hep.dataforge.vis.spatial.three.output import hep.dataforge.vis.spatial.three.output
import hep.dataforge.vis.spatial.visible import hep.dataforge.vis.spatial.visible
import kotlinx.html.dom.append import org.w3c.dom.*
import kotlinx.html.js.p
import org.w3c.dom.DragEvent
import org.w3c.dom.HTMLDivElement
import org.w3c.dom.HTMLElement
import org.w3c.files.FileList import org.w3c.files.FileList
import org.w3c.files.FileReader import org.w3c.files.FileReader
import org.w3c.files.get import org.w3c.files.get
@ -81,17 +76,18 @@ private class GDMLDemoApp : Application {
} }
private fun message(message: String?) { private fun message(message: String?) {
document.getElementById("messages")?.let { element -> console.log(message)
if (message == null) { // document.getElementById("messages")?.let { element ->
element.clear() // if (message == null) {
} else { // element.clear()
element.append { // } else {
p { // element.append {
+message // p {
} // +message
} // }
} // }
} // }
// }
} }
private val gdmlConfiguration: GDMLTransformer.() -> Unit = { private val gdmlConfiguration: GDMLTransformer.() -> Unit = {
@ -128,7 +124,7 @@ private class GDMLDemoApp : Application {
//val url = URL("https://drive.google.com/open?id=1w5e7fILMN83JGgB8WANJUYm8OW2s0WVO") //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 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 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") val editorElement = document.getElementById("editor") ?: error("Element with id 'editor' not found on page")
canvasElement.clear() canvasElement.clear()
@ -136,15 +132,14 @@ private class GDMLDemoApp : Application {
val action: (name: String, data: String) -> Unit = { name, data -> val action: (name: String, data: String) -> Unit = { name, data ->
canvasElement.clear() canvasElement.clear()
spinner(true) spinner(true)
val visual: VisualObject3D = when {
name.endsWith(".gdml") || name.endsWith(".xml") -> {
message("Loading GDML") message("Loading GDML")
val gdml = GDML.format.parse(GDML.serializer(), data) val gdml = GDML.format.parse(GDML.serializer(), data)
message("Converting GDML into DF-VIS format") message("Converting GDML into DF-VIS format")
gdml.toVisual(gdmlConfiguration)
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(".json") -> VisualGroup3D.fromJson(data)
else -> { else -> {
window.alert("File extension is not recognized: $name") window.alert("File extension is not recognized: $name")
error("File extension is not recognized: $name") error("File extension is not recognized: $name")
@ -187,9 +182,9 @@ private class GDMLDemoApp : Application {
// canvas.clickListener = ::selectElement // canvas.clickListener = ::selectElement
//tree.visualObjectTree(visual, editor::propertyEditor) //tree.visualObjectTree(visual, editor::propertyEditor)
treeElement.displayObjectTree(visual) { name -> treeElement.displayObjectTree(visual) { treeName ->
selectElement(name) selectElement(treeName)
canvas.highlight(name) canvas.highlight(treeName)
} }
canvas.render(visual) canvas.render(visual)
@ -204,6 +199,19 @@ private class GDMLDemoApp : Application {
addEventListener("dragover", { handleDragOver(it as DragEvent) }, false) addEventListener("dragover", { handleDragOver(it as DragEvent) }, false)
addEventListener("drop", { loadData(it as DragEvent, action) }, 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)
}
} }
} }

View File

@ -1,3 +1,7 @@
.drop_zone {
outline: 1px solid orange;
}
.loader { .loader {
border: 16px solid #f3f3f3; /* Light grey */ border: 16px solid #f3f3f3; /* Light grey */
border-top: 16px solid #3498db; /* Blue */ border-top: 16px solid #3498db; /* Blue */

View File

@ -4,34 +4,45 @@
<meta charset="utf-8"> <meta charset="utf-8">
<!-- <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">--> <!-- <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">-->
<title>Three js demo for particle physics</title> <title>Three js demo for particle physics</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.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> <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> </head>
<body class="testApp"> <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"> <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 Load data
<br/> <br/>
(drag file here) (drag file here)
</div> </div>
<div class="container">
<h1>GDML demo</h1>
</div> </div>
<!--<div class="container loader" id="loader" style="display:none;"></div>--> <div class="overflow-auto" id="tree"></div>
<div class="container animate-bottom" id="messages"></div> </div>
<div class="container-fluid">
<div class="row">
<div class="col-lg-3" id="tree"></div>
<div class="col-lg-6"> <div class="col-lg-6">
<div class="row" id="layers"></div> <div id="canvas"></div>
<div class="row container" id="canvas"></div>
</div> </div>
<div class="col-lg-3" id="editor"></div> <div class="col-lg-3">
<div class="row" id="config"></div>
<div class="row" id="editor"></div>
</div> </div>
</div> </div>
</body> </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>
</html> </html>

View File

@ -4,15 +4,13 @@ import hep.dataforge.context.Global
import hep.dataforge.vis.fx.editor.VisualObjectEditorFragment import hep.dataforge.vis.fx.editor.VisualObjectEditorFragment
import hep.dataforge.vis.fx.editor.VisualObjectTreeFragment import hep.dataforge.vis.fx.editor.VisualObjectTreeFragment
import hep.dataforge.vis.spatial.Material3D 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.FX3DPlugin
import hep.dataforge.vis.spatial.fx.FXCanvas3D 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.geometry.Orientation
import javafx.scene.Parent import javafx.scene.Parent
import javafx.stage.FileChooser import javafx.stage.FileChooser
import scientifik.gdml.GDML
import tornadofx.* import tornadofx.*
class GDMLDemoApp : App(GDMLView::class) class GDMLDemoApp : App(GDMLView::class)
@ -36,26 +34,12 @@ class GDMLView : View() {
override val root: Parent = borderpane { override val root: Parent = borderpane {
top { top {
buttonbar { buttonbar {
button("Load GDML") { button("Load GDML/json") {
action { action {
val file = chooseFile("Select a GDML file", filters = gdmlFilter).firstOrNull() val file = chooseFile("Select a GDML/json file", filters = fileNameFilter).firstOrNull()
if (file != null) { ?: return@action
val obj = GDML.readFile(file.toPath()).toVisual { val visual: VisualGroup3D = Visual3D.readFile(file)
lUnit = LUnit.CM canvas.render(visual)
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)
}
} }
} }
} }
@ -68,8 +52,11 @@ class GDMLView : View() {
} }
companion object { companion object {
private val gdmlFilter = arrayOf( private val fileNameFilter = arrayOf(
FileChooser.ExtensionFilter("GDML", "*.gdml", "*.xml") FileChooser.ExtensionFilter("GDML", "*.gdml", "*.xml"),
FileChooser.ExtensionFilter("JSON", "*.json"),
FileChooser.ExtensionFilter("JSON.ZIP", "*.json.zip"),
FileChooser.ExtensionFilter("JSON.GZ", "*.json.gz")
) )
} }
} }

View File

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

View File

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

View File

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

View File

@ -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_COLOR_KEY
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_OPACITY_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.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
import hep.dataforge.vis.spatial.VisualObject3D.Companion.VISIBLE_KEY import hep.dataforge.vis.spatial.VisualObject3D.Companion.VISIBLE_KEY
import hep.dataforge.vis.spatial.three.ThreePlugin import hep.dataforge.vis.spatial.three.ThreePlugin
@ -35,13 +35,13 @@ import org.w3c.dom.HTMLElement
import kotlin.browser.document import kotlin.browser.document
import kotlin.dom.clear import kotlin.dom.clear
private class GDMLDemoApp : Application { private class MMDemoApp : Application {
private val model = Model() private val model = Model()
private val connection = HttpClient { private val connection = HttpClient {
install(JsonFeature) { install(JsonFeature) {
serializer = KotlinxSerializer(Visual3DPlugin.json) serializer = KotlinxSerializer(Visual3D.json)
} }
} }
@ -122,5 +122,5 @@ private class GDMLDemoApp : Application {
} }
fun main() { fun main() {
startApplication(::GDMLDemoApp) startApplication(::MMDemoApp)
} }

View File

@ -1,7 +1,7 @@
package ru.mipt.npm.muon.monitor.server 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.Application
import io.ktor.application.call import io.ktor.application.call
import io.ktor.application.install import io.ktor.application.install
@ -35,7 +35,7 @@ fun Application.module() {
install(DefaultHeaders) install(DefaultHeaders)
install(CallLogging) install(CallLogging)
install(ContentNegotiation) { install(ContentNegotiation) {
serialization(json = Visual3DPlugin.json) serialization(json = Visual3D.json)
} }
install(Routing) { install(Routing) {
get("/event") { get("/event") {

View File

@ -7,6 +7,7 @@ import hep.dataforge.meta.number
import hep.dataforge.names.plus import hep.dataforge.names.plus
import hep.dataforge.names.startsWith import hep.dataforge.names.startsWith
import hep.dataforge.vis.common.getProperty import hep.dataforge.vis.common.getProperty
import hep.dataforge.vis.common.set
import hep.dataforge.vis.common.setProperty import hep.dataforge.vis.common.setProperty
import hep.dataforge.vis.spatial.* import hep.dataforge.vis.spatial.*
import hep.dataforge.vis.spatial.VisualObject3D.Companion.GEOMETRY_KEY import hep.dataforge.vis.spatial.VisualObject3D.Companion.GEOMETRY_KEY

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

BIN
doc/resources/gdml-demo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB