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

View File

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

View File

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

View File

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

View File

@ -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<*>? {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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()
override fun provideTop(target: String): Map<Name, Any> = if (target == Visual.VISUAL_FACTORY_TYPE) {
mapOf(Box.TYPE_NAME.toName() to Box)
} 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 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)

View File

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

View File

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

View File

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

View File

@ -51,7 +51,9 @@ object RemoveSingleChild : VisualTreeTransform<VisualGroup3D>() {
}
replaceChildren()
prototypes?.replaceChildren()
prototypes {
replaceChildren()
}
}
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.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> {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,8 @@ 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") {
@ -46,7 +47,63 @@ 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
)
}
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(),
group
)
@ -81,7 +138,7 @@ fun Element.displayCanvasControls(canvas: ThreeCanvas, block: TagConsumer<HTMLEl
}
}
}
}
}*/
block()
}
}

View File

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

View File

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

View File

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

View File

@ -27,6 +27,11 @@ kotlin {
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_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)
val visual: VisualObject3D = when {
name.endsWith(".gdml") || name.endsWith(".xml") -> {
message("Loading GDML")
val gdml = GDML.format.parse(GDML.serializer(), data)
message("Converting GDML into DF-VIS format")
val visual: VisualObject3D = when {
name.endsWith(".gdml") || name.endsWith(".xml") -> gdml.toVisual(gdmlConfiguration)
name.endsWith(".json") -> {
Visual3DPlugin.json.parse(VisualGroup3D.serializer(), data).apply { attachChildren() }
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)
}
}
}

View File

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

View File

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

View File

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

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

View File

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

View File

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

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