forked from kscience/visionforge
commit
8094f02f06
17
.github/workflows/gradle.yml
vendored
Normal file
17
.github/workflows/gradle.yml
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
name: Gradle build
|
||||||
|
|
||||||
|
on: [push]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
- name: Set up JDK 11
|
||||||
|
uses: actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
java-version: 11
|
||||||
|
- name: Build with Gradle
|
||||||
|
run: ./gradlew build
|
74
README.md
74
README.md
@ -1,11 +1,73 @@
|
|||||||
# DataForge plugins for visualisation
|
[![JetBrains Research](https://jb.gg/badges/research.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub)
|
||||||
|
|
||||||
## Common visualisation objects
|
# DataForge Visualisation Platform
|
||||||
|
|
||||||
## JavaFX utilities for meta manipulations
|
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. Currently, the main application is 3D visualization for particle
|
||||||
|
physics experiments.
|
||||||
|
|
||||||
## 3D visualisation
|
The project is developed as a Kotlin multiplatform application, currently
|
||||||
|
targeting browser JavaScript and JVM.
|
||||||
|
|
||||||
Includes common discription and serializers, JavaFX and Three.js implementations.
|
Main features:
|
||||||
|
- 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
|
||||||
|
- Settings export and import
|
||||||
|
- Multiple platform support
|
||||||
|
|
||||||
## GDML bindings for 3D visualisation (to be moved to gdml project)
|
|
||||||
|
## Modules contained in this repository:
|
||||||
|
|
||||||
|
|
||||||
|
### dataforge-vis-common
|
||||||
|
|
||||||
|
Common visualisation objects such as VisualObject and VisualGroup.
|
||||||
|
|
||||||
|
|
||||||
|
### dataforge-vis-spatial
|
||||||
|
|
||||||
|
Includes common description and serializers for 3D visualisation, JavaFX and Three.js implementations.
|
||||||
|
|
||||||
|
|
||||||
|
### dataforge-vis-spatial-gdml
|
||||||
|
|
||||||
|
GDML bindings for 3D visualisation (to be moved to gdml project).
|
||||||
|
|
||||||
|
|
||||||
|
### dataforge-vis-jsroot
|
||||||
|
|
||||||
|
Some JSROOT bindings.
|
||||||
|
|
||||||
|
Note: Currently, this part is experimental and put here for completeness. This module may not build.
|
||||||
|
|
||||||
|
|
||||||
|
### demo
|
||||||
|
|
||||||
|
Several demonstrations of using the dataforge-vis framework:
|
||||||
|
|
||||||
|
##### spatial-showcase
|
||||||
|
|
||||||
|
Contains a simple demonstration (grid with a few shapes that you can rotate, move camera, etc.).
|
||||||
|
|
||||||
|
To see the demo: run `demo/spatial-showcase/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.
|
||||||
|
|
||||||
|
##### muon-monitor
|
||||||
|
|
||||||
|
A full-stack application example, showing the
|
||||||
|
[Muon Monitor](http://npm.mipt.ru/projects/physics.html#mounMonitor) experiment set-up.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
Visualization example for geometry defined as GDML file.
|
||||||
|
@ -1,34 +1,38 @@
|
|||||||
val dataforgeVersion by extra("0.1.3")
|
import scientifik.useSerialization
|
||||||
|
|
||||||
plugins{
|
val dataforgeVersion by extra("0.1.5-dev-6")
|
||||||
val kotlinVersion = "1.3.50-eap-5"
|
|
||||||
|
plugins {
|
||||||
|
val kotlinVersion = "1.3.61"
|
||||||
|
val toolsVersion = "0.3.2"
|
||||||
|
|
||||||
kotlin("jvm") version kotlinVersion apply false
|
kotlin("jvm") version kotlinVersion apply false
|
||||||
id("kotlin2js") version kotlinVersion apply false
|
|
||||||
id("kotlin-dce-js") version kotlinVersion apply false
|
id("kotlin-dce-js") version kotlinVersion apply false
|
||||||
id("org.jetbrains.kotlin.frontend") version "0.0.45" apply false
|
id("scientifik.mpp") version toolsVersion apply false
|
||||||
id("scientifik.mpp") version "0.1.4" apply false
|
id("scientifik.jvm") version toolsVersion apply false
|
||||||
id("scientifik.jvm") version "0.1.4" apply false
|
id("scientifik.js") version toolsVersion apply false
|
||||||
id("scientifik.js") version "0.1.4" apply false
|
id("scientifik.publish") version toolsVersion apply false
|
||||||
id("scientifik.publish") version "0.1.4" apply false
|
id("org.openjfx.javafxplugin") version "0.0.8" apply false
|
||||||
id("org.openjfx.javafxplugin") version "0.0.7" apply false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
jcenter()
|
|
||||||
maven("https://kotlin.bintray.com/kotlinx")
|
|
||||||
maven("http://npm.mipt.ru:8081/artifactory/gradle-dev-local")
|
|
||||||
maven("https://kotlin.bintray.com/js-externals")
|
|
||||||
maven("https://dl.bintray.com/pdvrieze/maven")
|
maven("https://dl.bintray.com/pdvrieze/maven")
|
||||||
maven("https://dl.bintray.com/kotlin/kotlin-eap")
|
maven("http://maven.jzy3d.org/releases")
|
||||||
|
maven("https://kotlin.bintray.com/js-externals")
|
||||||
|
// maven("https://dl.bintray.com/gbaldeck/kotlin")
|
||||||
|
// maven("https://dl.bintray.com/rjaros/kotlin")
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "hep.dataforge"
|
group = "hep.dataforge"
|
||||||
version = "0.1.0-dev"
|
version = "0.1.0-dev"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
subprojects{
|
||||||
|
this.useSerialization()
|
||||||
|
}
|
||||||
|
|
||||||
val githubProject by extra("dataforge-vis")
|
val githubProject by extra("dataforge-vis")
|
||||||
val bintrayRepo by extra("dataforge")
|
val bintrayRepo by extra("dataforge")
|
||||||
|
|
||||||
|
@ -1,25 +1,50 @@
|
|||||||
|
import org.openjfx.gradle.JavaFXOptions
|
||||||
|
import scientifik.useSerialization
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("scientifik.mpp")
|
id("scientifik.mpp")
|
||||||
}
|
id("org.openjfx.javafxplugin")
|
||||||
|
|
||||||
scientifik{
|
|
||||||
serialization = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val dataforgeVersion: String by rootProject.extra
|
val dataforgeVersion: String by rootProject.extra
|
||||||
|
//val kvisionVersion: String by rootProject.extra("2.0.0-M1")
|
||||||
|
|
||||||
|
useSerialization()
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
|
jvm{
|
||||||
|
withJava()
|
||||||
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
val commonMain by getting {
|
commonMain{
|
||||||
dependencies {
|
dependencies {
|
||||||
api("hep.dataforge:dataforge-output:$dataforgeVersion")
|
api("hep.dataforge:dataforge-output:$dataforgeVersion")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val jsMain by getting {
|
jvmMain{
|
||||||
|
dependencies {
|
||||||
|
api("no.tornado:tornadofx:1.7.19")
|
||||||
|
//api("no.tornado:tornadofx-controlsfx:0.1.1")
|
||||||
|
api("de.jensd:fontawesomefx-fontawesome:4.7.0-11"){
|
||||||
|
exclude(group = "org.openjfx")
|
||||||
|
}
|
||||||
|
api("de.jensd:fontawesomefx-commons:11.0"){
|
||||||
|
exclude(group = "org.openjfx")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jsMain{
|
||||||
dependencies {
|
dependencies {
|
||||||
api("hep.dataforge:dataforge-output-html:$dataforgeVersion")
|
api("hep.dataforge:dataforge-output-html:$dataforgeVersion")
|
||||||
api(npm("text-encoding"))
|
api(npm("bootstrap","4.4.1"))
|
||||||
|
implementation(npm("jsoneditor"))
|
||||||
|
implementation(npm("file-saver"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
configure<JavaFXOptions> {
|
||||||
|
modules("javafx.controls")
|
||||||
|
}
|
||||||
|
@ -0,0 +1,112 @@
|
|||||||
|
package hep.dataforge.vis.common
|
||||||
|
|
||||||
|
import hep.dataforge.meta.MetaItem
|
||||||
|
import hep.dataforge.names.*
|
||||||
|
import kotlinx.serialization.Transient
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract implementation of mutable group of [VisualObject]
|
||||||
|
*/
|
||||||
|
abstract class AbstractVisualGroup : AbstractVisualObject(), MutableVisualGroup {
|
||||||
|
|
||||||
|
//protected abstract val _children: MutableMap<NameToken, T>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map of top level named children
|
||||||
|
*/
|
||||||
|
abstract override val children: Map<NameToken, VisualObject>
|
||||||
|
|
||||||
|
override fun propertyChanged(name: Name, before: MetaItem<*>?, after: MetaItem<*>?) {
|
||||||
|
super.propertyChanged(name, before, after)
|
||||||
|
forEach {
|
||||||
|
it.propertyChanged(name, before, after)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Consider renaming to `StructureChangeListener` (singular)
|
||||||
|
private data class StructureChangeListeners(val owner: Any?, val callback: (Name, VisualObject?) -> Unit)
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
private val structureChangeListeners = HashSet<StructureChangeListeners>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add listener for children change
|
||||||
|
*/
|
||||||
|
override fun onChildrenChange(owner: Any?, action: (Name, VisualObject?) -> Unit) {
|
||||||
|
structureChangeListeners.add(StructureChangeListeners(owner, action))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove children change listener
|
||||||
|
*/
|
||||||
|
override fun removeChildrenChangeListener(owner: Any?) {
|
||||||
|
structureChangeListeners.removeAll { it.owner === owner }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Propagate children change event upwards
|
||||||
|
*/
|
||||||
|
protected fun childrenChanged(name: Name, child: VisualObject?) {
|
||||||
|
structureChangeListeners.forEach { it.callback(name, child) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a child with given name token
|
||||||
|
*/
|
||||||
|
protected abstract fun removeChild(token: NameToken)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add, remove or replace child with given name
|
||||||
|
*/
|
||||||
|
protected abstract fun setChild(token: NameToken, child: VisualObject)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a static child. Statics could not be found by name, removed or replaced
|
||||||
|
*/
|
||||||
|
protected open fun addStatic(child: VisualObject) =
|
||||||
|
setChild(NameToken("@static(${child.hashCode()})"), child)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively create a child group
|
||||||
|
*/
|
||||||
|
protected abstract fun createGroup(name: Name): 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?) {
|
||||||
|
when {
|
||||||
|
name.isEmpty() -> {
|
||||||
|
if (child != null) {
|
||||||
|
addStatic(child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
name.length == 1 -> {
|
||||||
|
val token = name.first()!!
|
||||||
|
if (child == null) {
|
||||||
|
removeChild(token)
|
||||||
|
} else {
|
||||||
|
setChild(token, child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
//TODO add safety check
|
||||||
|
val parent = (get(name.cutLast()) as? MutableVisualGroup) ?: createGroup(name.cutLast())
|
||||||
|
parent[name.last()!!.asName()] = 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
package hep.dataforge.vis.common
|
||||||
|
|
||||||
|
import hep.dataforge.meta.*
|
||||||
|
import hep.dataforge.names.Name
|
||||||
|
import hep.dataforge.names.asName
|
||||||
|
import hep.dataforge.vis.common.VisualObject.Companion.STYLE_KEY
|
||||||
|
import kotlinx.serialization.Transient
|
||||||
|
|
||||||
|
internal data class PropertyListener(
|
||||||
|
val owner: Any? = null,
|
||||||
|
val action: (name: Name, oldItem: MetaItem<*>?, newItem: MetaItem<*>?) -> Unit
|
||||||
|
)
|
||||||
|
|
||||||
|
abstract class AbstractVisualObject : VisualObject {
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
override var parent: VisualObject? = null
|
||||||
|
|
||||||
|
protected abstract var properties: Config?
|
||||||
|
|
||||||
|
override var styles: List<String>
|
||||||
|
get() = properties?.get(STYLE_KEY).stringList
|
||||||
|
set(value) {
|
||||||
|
//val allStyles = (field + value).distinct()
|
||||||
|
setProperty(STYLE_KEY, value)
|
||||||
|
updateStyles(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun updateStyles(names: List<String>) {
|
||||||
|
names.mapNotNull { findStyle(it) }.asSequence()
|
||||||
|
.flatMap { it.items.asSequence() }
|
||||||
|
.distinctBy { it.key }
|
||||||
|
.forEach {
|
||||||
|
propertyChanged(it.key.asName(), null, it.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The config is initialized and assigned on-demand.
|
||||||
|
* To avoid unnecessary allocations, one should access [properties] via [getProperty] instead.
|
||||||
|
*/
|
||||||
|
override val config: Config
|
||||||
|
get() = properties ?: Config().also { config ->
|
||||||
|
properties = config.apply { onChange(this, ::propertyChanged) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
private val listeners = HashSet<PropertyListener>()
|
||||||
|
|
||||||
|
override fun propertyChanged(name: Name, before: MetaItem<*>?, after: MetaItem<*>?) {
|
||||||
|
if (before != after) {
|
||||||
|
for (l in listeners) {
|
||||||
|
l.action(name, before, after)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPropertyChange(owner: Any?, action: (Name, before: MetaItem<*>?, after: MetaItem<*>?) -> Unit) {
|
||||||
|
listeners.add(PropertyListener(owner, action))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeChangeListener(owner: Any?) {
|
||||||
|
listeners.removeAll { it.owner == owner }
|
||||||
|
}
|
||||||
|
|
||||||
|
private var styleCache: Meta? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collect all styles for this object in a laminate
|
||||||
|
*/
|
||||||
|
protected val mergedStyles: Meta
|
||||||
|
get() = styleCache ?: findAllStyles().merge().also {
|
||||||
|
styleCache = it
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun allProperties(): Laminate = Laminate(properties, mergedStyles)
|
||||||
|
|
||||||
|
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? {
|
||||||
|
return if (inherit) {
|
||||||
|
properties?.get(name) ?: mergedStyles[name] ?: parent?.getProperty(name, inherit)
|
||||||
|
} else {
|
||||||
|
properties?.get(name) ?: mergedStyles[name]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//fun VisualObject.findStyle(styleName: Name): Meta? {
|
||||||
|
// if (this is VisualGroup) {
|
||||||
|
// val style = resolveStyle(styleName)
|
||||||
|
// if (style != null) return style
|
||||||
|
// }
|
||||||
|
// return parent?.findStyle(styleName)
|
||||||
|
//}
|
@ -1,7 +1,13 @@
|
|||||||
package hep.dataforge.vis.common
|
package hep.dataforge.vis.common
|
||||||
|
|
||||||
|
import hep.dataforge.meta.*
|
||||||
|
import hep.dataforge.values.ValueType
|
||||||
|
import hep.dataforge.values.int
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Taken from https://github.com/markaren/three.kt/blob/master/threejs-wrapper/src/main/kotlin/info/laht/threekt/math/ColorConstants.kt
|
* Definitions of common colors. Taken from
|
||||||
|
* https://github.com/markaren/three.kt/blob/master/threejs-wrapper/src/main/kotlin/info/laht/threekt/math/ColorConstants.kt
|
||||||
*/
|
*/
|
||||||
object Colors {
|
object Colors {
|
||||||
const val aliceblue = 0xF0F8FF
|
const val aliceblue = 0xF0F8FF
|
||||||
@ -174,4 +180,63 @@ object Colors {
|
|||||||
const val whitesmoke = 0xF5F5F5
|
const val whitesmoke = 0xF5F5F5
|
||||||
const val yellow = 0xFFFF00
|
const val yellow = 0xFFFF00
|
||||||
const val yellowgreen = 0x9ACD32
|
const val yellowgreen = 0x9ACD32
|
||||||
|
|
||||||
|
const val RED_KEY = "red"
|
||||||
|
const val GREEN_KEY = "green"
|
||||||
|
const val BLUE_KEY = "blue"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert color represented as Meta to string of format #rrggbb
|
||||||
|
*/
|
||||||
|
fun fromMeta(item: MetaItem<*>): String {
|
||||||
|
return when (item) {
|
||||||
|
is MetaItem.NodeItem<*> -> {
|
||||||
|
val node = item.node
|
||||||
|
rgbToString(
|
||||||
|
node[RED_KEY].number?.toByte()?.toUByte() ?: 0u,
|
||||||
|
node[GREEN_KEY].number?.toByte()?.toUByte() ?: 0u,
|
||||||
|
node[BLUE_KEY].number?.toByte()?.toUByte() ?: 0u
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is MetaItem.ValueItem -> {
|
||||||
|
if (item.value.type == ValueType.NUMBER) {
|
||||||
|
rgbToString(item.value.int)
|
||||||
|
} else {
|
||||||
|
item.value.string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert Int color to string of format #rrggbb
|
||||||
|
*/
|
||||||
|
fun rgbToString(rgb: Int): String {
|
||||||
|
val string = rgb.toString(16).padStart(6, '0')
|
||||||
|
return "#" + string.substring(max(0, string.length - 6))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert three bytes representing color to string of format #rrggbb
|
||||||
|
*/
|
||||||
|
fun rgbToString(red: UByte, green: UByte, blue: UByte): String {
|
||||||
|
fun colorToString(color: UByte): String {
|
||||||
|
return color.toString(16).padStart(2, '0')
|
||||||
|
}
|
||||||
|
return buildString {
|
||||||
|
append("#")
|
||||||
|
append(colorToString(red))
|
||||||
|
append(colorToString(green))
|
||||||
|
append(colorToString(blue))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert three bytes representing color to Meta
|
||||||
|
*/
|
||||||
|
fun rgbToMeta(r: UByte, g: UByte, b: UByte): Meta = buildMeta {
|
||||||
|
RED_KEY put r.toInt()
|
||||||
|
GREEN_KEY put g.toInt()
|
||||||
|
BLUE_KEY put b.toInt()
|
||||||
|
}
|
||||||
}
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
@file:UseSerializers(MetaSerializer::class)
|
||||||
|
|
||||||
|
package hep.dataforge.vis.common
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class StyleSheet() {
|
||||||
|
@Transient
|
||||||
|
internal var owner: VisualObject? = null
|
||||||
|
|
||||||
|
constructor(owner: VisualObject) : this() {
|
||||||
|
this.owner = owner
|
||||||
|
}
|
||||||
|
|
||||||
|
private val styleMap = HashMap<String, Meta>()
|
||||||
|
|
||||||
|
val items: Map<String, Meta> get() = styleMap
|
||||||
|
|
||||||
|
operator fun get(key: String): Meta? {
|
||||||
|
return styleMap[key] ?: (owner?.parent as? VisualGroup)?.styleSheet?.get(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a style without notifying
|
||||||
|
*/
|
||||||
|
fun define(key: String, style: Meta?) {
|
||||||
|
if (style == null) {
|
||||||
|
styleMap.remove(key)
|
||||||
|
} else {
|
||||||
|
styleMap[key] = style
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun set(key: String, style: Meta?) {
|
||||||
|
val oldStyle = styleMap[key]
|
||||||
|
define(key, style)
|
||||||
|
owner?.styleChanged(key, oldStyle, style)
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun set(key: String, builder: MetaBuilder.() -> Unit) {
|
||||||
|
val newStyle = get(key)?.let { buildMeta(it, builder) } ?: buildMeta(builder)
|
||||||
|
set(key, newStyle.seal())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun VisualObject.styleChanged(key: String, oldStyle: Meta?, newStyle: Meta?) {
|
||||||
|
if (styles.contains(key)) {
|
||||||
|
//TODO optimize set concatenation
|
||||||
|
val tokens: Collection<Name> = ((oldStyle?.items?.keys ?: emptySet()) + (newStyle?.items?.keys ?: emptySet()))
|
||||||
|
.map { it.asName() }
|
||||||
|
tokens.forEach { parent?.propertyChanged(it, oldStyle?.get(it), newStyle?.get(it)) }
|
||||||
|
}
|
||||||
|
if (this is VisualGroup) {
|
||||||
|
this.forEach { it.styleChanged(key, oldStyle, newStyle) }
|
||||||
|
}
|
||||||
|
}
|
@ -1,131 +1,89 @@
|
|||||||
package hep.dataforge.vis.common
|
package hep.dataforge.vis.common
|
||||||
|
|
||||||
import hep.dataforge.meta.Config
|
import hep.dataforge.names.*
|
||||||
import hep.dataforge.meta.MetaBuilder
|
|
||||||
import hep.dataforge.meta.MetaItem
|
|
||||||
import hep.dataforge.names.Name
|
|
||||||
import hep.dataforge.names.toName
|
|
||||||
import hep.dataforge.provider.Provider
|
import hep.dataforge.provider.Provider
|
||||||
import kotlinx.serialization.Transient
|
|
||||||
import kotlin.collections.set
|
|
||||||
|
|
||||||
open class VisualGroup<T : VisualObject> : AbstractVisualObject(), Iterable<T>, Provider {
|
/**
|
||||||
|
* Represents a group of [VisualObject] instances
|
||||||
protected open val namedChildren: MutableMap<Name, T> = HashMap()
|
*/
|
||||||
protected open val unnamedChildren: MutableList<T> = ArrayList()
|
interface VisualGroup : Provider, Iterable<VisualObject>, VisualObject {
|
||||||
|
/**
|
||||||
override var properties: Config? = null
|
* A map of top level named children
|
||||||
|
*/
|
||||||
|
val children: Map<NameToken, VisualObject>
|
||||||
|
|
||||||
override val defaultTarget: String get() = VisualObject.TYPE
|
override val defaultTarget: String get() = VisualObject.TYPE
|
||||||
|
|
||||||
override fun iterator(): Iterator<T> = (namedChildren.values + unnamedChildren).iterator()
|
val styleSheet: StyleSheet?
|
||||||
|
|
||||||
override fun provideTop(target: String): Map<Name, Any> {
|
override fun provideTop(target: String): Map<Name, Any> =
|
||||||
return when (target) {
|
when (target) {
|
||||||
VisualObject.TYPE -> namedChildren
|
VisualObject.TYPE -> children.flatMap { (key, value) ->
|
||||||
|
val res: Map<Name, Any> = if (value is VisualGroup) {
|
||||||
|
value.provideTop(target).mapKeys { key + it.key }
|
||||||
|
} else {
|
||||||
|
mapOf(key.asName() to value)
|
||||||
|
}
|
||||||
|
res.entries
|
||||||
|
}.associate { it.toPair() }
|
||||||
|
STYLE_TARGET -> styleSheet?.items?.mapKeys { it.key.toName() } ?: emptyMap()
|
||||||
else -> emptyMap()
|
else -> emptyMap()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun propertyChanged(name: Name, before: MetaItem<*>?, after: MetaItem<*>?) {
|
|
||||||
super.propertyChanged(name, before, after)
|
|
||||||
forEach {
|
|
||||||
it.propertyChanged(name, before, after)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private data class Listener<T : VisualObject>(val owner: Any?, val callback: (Name?, T?) -> Unit)
|
|
||||||
|
|
||||||
@Transient
|
|
||||||
private val listeners = HashSet<Listener<T>>()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add listener for children change
|
* Iterate over children of this group
|
||||||
*/
|
*/
|
||||||
fun onChildrenChange(owner: Any?, action: (Name?, T?) -> Unit) {
|
override fun iterator(): Iterator<VisualObject> = children.values.iterator()
|
||||||
listeners.add(Listener(owner, action))
|
|
||||||
|
operator fun get(name: Name): VisualObject? {
|
||||||
|
return when {
|
||||||
|
name.isEmpty() -> this
|
||||||
|
name.length == 1 -> children[name.first()!!]
|
||||||
|
else -> (children[name.first()!!] as? VisualGroup)?.get(name.cutFirst())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A fix for serialization bug that writes all proper parents inside the tree after deserialization
|
||||||
|
*/
|
||||||
|
fun attachChildren() {
|
||||||
|
styleSheet?.owner = this
|
||||||
|
this.children.values.forEach {
|
||||||
|
it.parent = this
|
||||||
|
(it as? VisualGroup)?.attachChildren()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val STYLE_TARGET = "style"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class StyleRef(val group: VisualGroup, val styleName: Name)
|
||||||
|
|
||||||
|
val VisualGroup.isEmpty: Boolean get() = this.children.isEmpty()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mutable version of [VisualGroup]
|
||||||
|
*/
|
||||||
|
interface MutableVisualGroup : VisualGroup {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add listener for children structure change.
|
||||||
|
* @param owner the handler to properly remove listeners
|
||||||
|
* @param action First argument of the action is the name of changed child. Second argument is the new value of the object.
|
||||||
|
*/
|
||||||
|
fun onChildrenChange(owner: Any?, action: (Name, VisualObject?) -> Unit)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove children change listener
|
* Remove children change listener
|
||||||
*/
|
*/
|
||||||
fun removeChildrenChangeListener(owner: Any?) {
|
fun removeChildrenChangeListener(owner: Any?)
|
||||||
listeners.removeAll { it.owner === owner }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
operator fun set(name: Name, child: VisualObject?)
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
operator fun set(name: Name?, child: T?) {
|
|
||||||
when {
|
|
||||||
name != null -> {
|
|
||||||
if (child == null) {
|
|
||||||
namedChildren.remove(name)
|
|
||||||
} else {
|
|
||||||
if (child.parent == null) {
|
|
||||||
child.parent = this
|
|
||||||
} else {
|
|
||||||
error("Can't reassign existing parent for $child")
|
|
||||||
}
|
|
||||||
namedChildren[name] = child
|
|
||||||
}
|
|
||||||
listeners.forEach { it.callback(name, child) }
|
|
||||||
}
|
|
||||||
child != null -> add(child)
|
|
||||||
else -> error("Both key and child element are empty")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
operator fun set(key: String?, child: T?) = set(key?.asName(), child)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get named child by name
|
|
||||||
*/
|
|
||||||
operator fun get(name: Name): T? = namedChildren[name]
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get named child by string
|
|
||||||
*/
|
|
||||||
operator fun get(key: String): T? = namedChildren[key.toName()]
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get an unnamed child
|
|
||||||
*/
|
|
||||||
operator fun get(index: Int): T? = unnamedChildren[index]
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Append unnamed child
|
|
||||||
*/
|
|
||||||
fun add(child: T) {
|
|
||||||
if (child.parent == null) {
|
|
||||||
child.parent = this
|
|
||||||
} else {
|
|
||||||
error("Can't reassign existing parent for $child")
|
|
||||||
}
|
|
||||||
unnamedChildren.add(child)
|
|
||||||
listeners.forEach { it.callback(null, child) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* remove unnamed child
|
|
||||||
*/
|
|
||||||
fun remove(child: VisualObject) {
|
|
||||||
unnamedChildren.remove(child)
|
|
||||||
listeners.forEach { it.callback(null, null) }
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun MetaBuilder.updateChildren() {
|
|
||||||
//adding unnamed children
|
|
||||||
"unnamedChildren" to unnamedChildren.map { it.toMeta() }
|
|
||||||
//adding named children
|
|
||||||
namedChildren.forEach {
|
|
||||||
"children[${it.key}]" to it.value.toMeta()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun MetaBuilder.updateMeta() {
|
|
||||||
updateChildren()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
operator fun VisualGroup.get(str: String?) = get(str?.toName() ?: Name.EMPTY)
|
||||||
|
|
||||||
|
fun MutableVisualGroup.removeAll() = children.keys.map { it.asName() }.forEach { this[it] = null }
|
@ -2,18 +2,20 @@ 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.asName
|
||||||
|
import hep.dataforge.names.toName
|
||||||
import hep.dataforge.provider.Type
|
import hep.dataforge.provider.Type
|
||||||
import hep.dataforge.vis.common.VisualObject.Companion.TYPE
|
import hep.dataforge.vis.common.VisualObject.Companion.TYPE
|
||||||
import kotlinx.serialization.Transient
|
import kotlinx.serialization.Transient
|
||||||
|
|
||||||
private fun Laminate.withTop(meta: Meta): Laminate = Laminate(listOf(meta) + layers)
|
//private fun Laminate.withTop(meta: Meta): Laminate = Laminate(listOf(meta) + layers)
|
||||||
private fun Laminate.withBottom(meta: Meta): Laminate = Laminate(layers + meta)
|
//private fun Laminate.withBottom(meta: Meta): Laminate = Laminate(layers + meta)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A root type for display hierarchy
|
* A root type for display hierarchy
|
||||||
*/
|
*/
|
||||||
@Type(TYPE)
|
@Type(TYPE)
|
||||||
interface VisualObject : MetaRepr, Configurable {
|
interface VisualObject : Configurable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The parent object of this one. If null, this one is a root.
|
* The parent object of this one. If null, this one is a root.
|
||||||
@ -21,10 +23,17 @@ interface VisualObject : MetaRepr, Configurable {
|
|||||||
@Transient
|
@Transient
|
||||||
var parent: VisualObject?
|
var parent: VisualObject?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All properties including styles and prototypes if present, but without inheritance
|
||||||
|
*/
|
||||||
|
fun allProperties(): Laminate
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set property for this object
|
* Set property for this object
|
||||||
*/
|
*/
|
||||||
fun setProperty(name: Name, value: Any?)
|
fun setProperty(name: Name, value: Any?) {
|
||||||
|
config[name] = value
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get property including or excluding parent properties
|
* Get property including or excluding parent properties
|
||||||
@ -32,9 +41,11 @@ interface VisualObject : MetaRepr, Configurable {
|
|||||||
fun getProperty(name: Name, inherit: Boolean = true): MetaItem<*>?
|
fun getProperty(name: Name, inherit: Boolean = true): MetaItem<*>?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manually trigger property changed event. If [name] is empty, notify that the whole object is changed
|
* Trigger property invalidation event. If [name] is empty, notify that the whole object is changed
|
||||||
*/
|
*/
|
||||||
fun propertyChanged(name: Name, before: MetaItem<*>? = null, after: MetaItem<*>? = null): Unit
|
fun propertyChanged(name: Name, before: MetaItem<*>?, after: MetaItem<*>?): Unit
|
||||||
|
|
||||||
|
fun propertyInvalidated(name: Name) = propertyChanged(name, null, null)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add listener triggering on property change
|
* Add listener triggering on property change
|
||||||
@ -46,65 +57,67 @@ interface VisualObject : MetaRepr, Configurable {
|
|||||||
*/
|
*/
|
||||||
fun removeChangeListener(owner: Any?)
|
fun removeChangeListener(owner: Any?)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of names of styles applied to this object. Order matters. Not inherited
|
||||||
|
*/
|
||||||
|
var styles: List<String>
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val TYPE = "visual"
|
const val TYPE = "visual"
|
||||||
|
val STYLE_KEY = "@style".asName()
|
||||||
|
|
||||||
//const val META_KEY = "@meta"
|
//const val META_KEY = "@meta"
|
||||||
//const val TAGS_KEY = "@tags"
|
//const val TAGS_KEY = "@tags"
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal data class MetaListener(
|
/**
|
||||||
val owner: Any? = null,
|
* Get [VisualObject] property using key as a String
|
||||||
val action: (name: Name, oldItem: MetaItem<*>?, newItem: MetaItem<*>?) -> Unit
|
*/
|
||||||
)
|
fun VisualObject.getProperty(key: String, inherit: Boolean = true): MetaItem<*>? = getProperty(key.toName(), inherit)
|
||||||
|
|
||||||
abstract class AbstractVisualObject: VisualObject {
|
/**
|
||||||
|
* Set [VisualObject] property using key as a String
|
||||||
|
*/
|
||||||
|
fun VisualObject.setProperty(key: String, value: Any?) = setProperty(key.toName(), value)
|
||||||
|
|
||||||
@Transient
|
/**
|
||||||
override var parent: VisualObject? = null
|
* Add style name to the list of styles to be resolved later. The style with given name does not necessary exist at the moment.
|
||||||
|
*/
|
||||||
@Transient
|
fun VisualObject.useStyle(name: String) {
|
||||||
private val listeners = HashSet<MetaListener>()
|
styles = styles + name
|
||||||
|
|
||||||
override fun propertyChanged(name: Name, before: MetaItem<*>?, after: MetaItem<*>?) {
|
|
||||||
for (l in listeners) {
|
|
||||||
l.action(name, before, after)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPropertyChange(owner: Any?, action: (Name, before: MetaItem<*>?, after: MetaItem<*>?) -> Unit) {
|
|
||||||
listeners.add(MetaListener(owner, action))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun removeChangeListener(owner: Any?) {
|
|
||||||
listeners.removeAll { it.owner == owner }
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract var properties: Config?
|
|
||||||
override val config: Config
|
|
||||||
get() = properties ?: Config().also { config ->
|
|
||||||
properties = config
|
|
||||||
config.onChange(this, ::propertyChanged)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setProperty(name: Name, value: Any?) {
|
|
||||||
config[name] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? {
|
|
||||||
return if (inherit) {
|
|
||||||
properties?.get(name) ?: parent?.getProperty(name, inherit)
|
|
||||||
} else {
|
|
||||||
properties?.get(name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected open fun MetaBuilder.updateMeta() {}
|
|
||||||
|
|
||||||
override fun toMeta(): Meta = buildMeta {
|
|
||||||
"type" to this::class.simpleName
|
|
||||||
"properties" to properties
|
|
||||||
updateMeta()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//private tailrec fun VisualObject.topGroup(): VisualGroup? {
|
||||||
|
// val parent = this.parent
|
||||||
|
// return if (parent == null) {
|
||||||
|
// this as? VisualGroup
|
||||||
|
// }
|
||||||
|
// else {
|
||||||
|
// parent.topGroup()
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
///**
|
||||||
|
// * Add or update given style on a top-most reachable parent group and apply it to this object
|
||||||
|
// */
|
||||||
|
//fun VisualObject.useStyle(name: String, builder: MetaBuilder.() -> Unit) {
|
||||||
|
// val styleName = name.toName()
|
||||||
|
// topGroup()?.updateStyle(styleName, builder) ?: error("Can't find parent group for $this")
|
||||||
|
// useStyle(styleName)
|
||||||
|
//}
|
||||||
|
|
||||||
|
tailrec fun VisualObject.findStyle(name: String): Meta? =
|
||||||
|
(this as? VisualGroup)?.styleSheet?.get(name) ?: parent?.findStyle(name)
|
||||||
|
|
||||||
|
fun VisualObject.findAllStyles(): Laminate = Laminate(styles.mapNotNull(::findStyle))
|
||||||
|
|
||||||
|
//operator fun VisualObject.get(name: Name): VisualObject?{
|
||||||
|
// return when {
|
||||||
|
// name.isEmpty() -> this
|
||||||
|
// this is VisualGroup -> this[name]
|
||||||
|
// else -> null
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
@ -4,18 +4,18 @@ import hep.dataforge.meta.*
|
|||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.Name
|
||||||
import hep.dataforge.names.NameToken
|
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
|
||||||
import kotlin.properties.ReadWriteProperty
|
import kotlin.properties.ReadWriteProperty
|
||||||
import kotlin.reflect.KProperty
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
fun String.asName() = NameToken(this).asName()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A delegate for display object properties
|
* A delegate for display object properties
|
||||||
*/
|
*/
|
||||||
class DisplayObjectDelegate(
|
class VisualObjectDelegate(
|
||||||
val key: Name?,
|
val key: Name?,
|
||||||
val default: MetaItem<*>?,
|
val default: MetaItem<*>?,
|
||||||
val inherited: Boolean
|
val inherited: Boolean
|
||||||
@ -35,82 +35,84 @@ class DisplayObjectDelegate(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DisplayObjectDelegateWrapper<T>(
|
class VisualObjectDelegateWrapper<T>(
|
||||||
|
val obj: VisualObject,
|
||||||
val key: Name?,
|
val key: Name?,
|
||||||
val default: T,
|
val default: T,
|
||||||
val inherited: Boolean,
|
val inherited: Boolean,
|
||||||
val write: Config.(name: Name, value: T) -> Unit = { name, value -> set(name, value) },
|
val write: Config.(name: Name, value: T) -> Unit = { name, value -> set(name, value) },
|
||||||
val read: (MetaItem<*>?) -> T?
|
val read: (MetaItem<*>?) -> T?
|
||||||
) : ReadWriteProperty<VisualObject, T> {
|
) : ReadWriteProperty<Any?, T> {
|
||||||
|
|
||||||
//private var cachedName: Name? = null
|
//private var cachedName: Name? = null
|
||||||
|
|
||||||
override fun getValue(thisRef: VisualObject, property: KProperty<*>): T {
|
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
|
||||||
val name = key ?: property.name.asName()
|
val name = key ?: property.name.asName()
|
||||||
return if (inherited) {
|
return read(obj.getProperty(name,inherited))?:default
|
||||||
read(thisRef.getProperty(name))
|
|
||||||
} else {
|
|
||||||
read(thisRef.config[name])
|
|
||||||
} ?: default
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setValue(thisRef: VisualObject, property: KProperty<*>, value: T) {
|
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
|
||||||
val name = key ?: property.name.asName()
|
val name = key ?: property.name.asName()
|
||||||
thisRef.config[name] = value
|
obj.config[name] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun VisualObject.value(default: Value? = null, key: String? = null, inherited: Boolean = false) =
|
fun VisualObject.value(default: Value? = null, key: String? = null, inherited: Boolean = false) =
|
||||||
DisplayObjectDelegateWrapper(key?.asName(), default, inherited) { it.value }
|
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.value }
|
||||||
|
|
||||||
fun VisualObject.string(default: String? = null, key: String? = null, inherited: Boolean = false) =
|
fun VisualObject.string(default: String? = null, key: String? = null, inherited: Boolean = false) =
|
||||||
DisplayObjectDelegateWrapper(key?.asName(), default, inherited) { it.string }
|
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.string }
|
||||||
|
|
||||||
fun VisualObject.boolean(default: Boolean? = null, key: String? = null, inherited: Boolean = false) =
|
fun VisualObject.boolean(default: Boolean? = null, key: String? = null, inherited: Boolean = false) =
|
||||||
DisplayObjectDelegateWrapper(key?.asName(), default, inherited) { it.boolean }
|
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.boolean }
|
||||||
|
|
||||||
fun VisualObject.number(default: Number? = null, key: String? = null, inherited: Boolean = false) =
|
fun VisualObject.number(default: Number? = null, key: String? = null, inherited: Boolean = false) =
|
||||||
DisplayObjectDelegateWrapper(key?.asName(), default, inherited) { it.number }
|
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.number }
|
||||||
|
|
||||||
fun VisualObject.double(default: Double? = null, key: String? = null, inherited: Boolean = false) =
|
fun VisualObject.double(default: Double? = null, key: String? = null, inherited: Boolean = false) =
|
||||||
DisplayObjectDelegateWrapper(key?.asName(), default, inherited) { it.double }
|
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.double }
|
||||||
|
|
||||||
fun VisualObject.int(default: Int? = null, key: String? = null, inherited: Boolean = false) =
|
fun VisualObject.int(default: Int? = null, key: String? = null, inherited: Boolean = false) =
|
||||||
DisplayObjectDelegateWrapper(key?.asName(), default, inherited) { it.int }
|
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.int }
|
||||||
|
|
||||||
|
|
||||||
fun VisualObject.node(key: String? = null, inherited: Boolean = true) =
|
fun VisualObject.node(key: String? = null, inherited: Boolean = true) =
|
||||||
DisplayObjectDelegateWrapper(key?.asName(), null, inherited) { it.node }
|
VisualObjectDelegateWrapper(this, key?.toName(), null, inherited) { it.node }
|
||||||
|
|
||||||
fun VisualObject.item(key: String? = null, inherited: Boolean = true) =
|
fun VisualObject.item(key: String? = null, inherited: Boolean = true) =
|
||||||
DisplayObjectDelegateWrapper(key?.asName(), null, inherited) { it }
|
VisualObjectDelegateWrapper(this, key?.toName(), 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, key: String? = null, inherited: Boolean = false) =
|
||||||
DisplayObjectDelegateWrapper(key?.asName(), default, inherited) { it.string }
|
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.string }
|
||||||
|
|
||||||
@JvmName("safeBoolean")
|
@JvmName("safeBoolean")
|
||||||
fun VisualObject.boolean(default: Boolean, key: String? = null, inherited: Boolean = false) =
|
fun VisualObject.boolean(default: Boolean, key: String? = null, inherited: Boolean = false) =
|
||||||
DisplayObjectDelegateWrapper(key?.asName(), default, inherited) { it.boolean }
|
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.boolean }
|
||||||
|
|
||||||
@JvmName("safeNumber")
|
@JvmName("safeNumber")
|
||||||
fun VisualObject.number(default: Number, key: String? = null, inherited: Boolean = false) =
|
fun VisualObject.number(default: Number, key: String? = null, inherited: Boolean = false) =
|
||||||
DisplayObjectDelegateWrapper(key?.asName(), default, inherited) { it.number }
|
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.number }
|
||||||
|
|
||||||
@JvmName("safeDouble")
|
@JvmName("safeDouble")
|
||||||
fun VisualObject.double(default: Double, key: String? = null, inherited: Boolean = false) =
|
fun VisualObject.double(default: Double, key: String? = null, inherited: Boolean = false) =
|
||||||
DisplayObjectDelegateWrapper(key?.asName(), default, inherited) { it.double }
|
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.double }
|
||||||
|
|
||||||
@JvmName("safeInt")
|
@JvmName("safeInt")
|
||||||
fun VisualObject.int(default: Int, key: String? = null, inherited: Boolean = false) =
|
fun VisualObject.int(default: Int, key: String? = null, inherited: Boolean = false) =
|
||||||
DisplayObjectDelegateWrapper(key?.asName(), default, inherited) { it.int }
|
VisualObjectDelegateWrapper(this, key?.toName(), 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, key: String? = null, inherited: Boolean = false) =
|
||||||
DisplayObjectDelegateWrapper(key?.let{ NameToken(it).asName()}, default, inherited) { item -> item.string?.let { enumValueOf<E>(it) } }
|
VisualObjectDelegateWrapper(
|
||||||
|
this,
|
||||||
|
key?.let { NameToken(it).asName() },
|
||||||
|
default,
|
||||||
|
inherited
|
||||||
|
) { item -> item.string?.let { enumValueOf<E>(it) } }
|
||||||
|
|
||||||
//merge properties
|
//merge properties
|
||||||
|
|
||||||
@ -118,11 +120,11 @@ fun <T> VisualObject.merge(
|
|||||||
key: String? = null,
|
key: String? = null,
|
||||||
transformer: (Sequence<MetaItem<*>>) -> T
|
transformer: (Sequence<MetaItem<*>>) -> T
|
||||||
): ReadOnlyProperty<VisualObject, T> {
|
): ReadOnlyProperty<VisualObject, T> {
|
||||||
return object : ReadOnlyProperty<VisualObject, T> {
|
return object : ReadOnlyProperty<Any?, T> {
|
||||||
override fun getValue(thisRef: VisualObject, property: KProperty<*>): T {
|
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
|
||||||
val name = key?.asName() ?: property.name.asName()
|
val name = key?.toName() ?: property.name.asName()
|
||||||
val sequence = sequence<MetaItem<*>> {
|
val sequence = sequence<MetaItem<*>> {
|
||||||
var thisObj: VisualObject? = thisRef
|
var thisObj: VisualObject? = this@merge
|
||||||
while (thisObj != null) {
|
while (thisObj != null) {
|
||||||
thisObj.config[name]?.let { yield(it) }
|
thisObj.config[name]?.let { yield(it) }
|
||||||
thisObj = thisObj.parent
|
thisObj = thisObj.parent
|
@ -30,7 +30,8 @@ class VisualPlugin(meta: Meta) : AbstractPlugin(meta) {
|
|||||||
companion object : PluginFactory<VisualPlugin> {
|
companion object : PluginFactory<VisualPlugin> {
|
||||||
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 VisualPlugin> = VisualPlugin::class
|
||||||
override fun invoke(meta: Meta): VisualPlugin = VisualPlugin(meta)
|
|
||||||
|
override fun invoke(meta: Meta, context: Context): VisualPlugin = VisualPlugin(meta)
|
||||||
|
|
||||||
const val VISUAL_FACTORY_TYPE = "visual.factory"
|
const val VISUAL_FACTORY_TYPE = "visual.factory"
|
||||||
}
|
}
|
||||||
|
@ -3,12 +3,18 @@ package hep.dataforge.vis.common
|
|||||||
import hep.dataforge.descriptors.ValueDescriptor
|
import hep.dataforge.descriptors.ValueDescriptor
|
||||||
import hep.dataforge.meta.*
|
import hep.dataforge.meta.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension property to access the "widget" key of [ValueDescriptor]
|
||||||
|
*/
|
||||||
var ValueDescriptor.widget: Meta
|
var ValueDescriptor.widget: Meta
|
||||||
get() = this.config["widget"].node?: EmptyMeta
|
get() = this.config["widget"].node?: EmptyMeta
|
||||||
set(value) {
|
set(value) {
|
||||||
this.config["widget"] = value
|
this.config["widget"] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension property to access the "widget.type" key of [ValueDescriptor]
|
||||||
|
*/
|
||||||
var ValueDescriptor.widgetType: String?
|
var ValueDescriptor.widgetType: String?
|
||||||
get() = this["widget.type"].string
|
get() = this["widget.type"].string
|
||||||
set(value) {
|
set(value) {
|
||||||
|
@ -1,14 +1,10 @@
|
|||||||
package hep.dataforge.vis.hmr
|
package hep.dataforge.js
|
||||||
|
|
||||||
import kotlin.browser.document
|
import kotlin.browser.document
|
||||||
import kotlin.dom.hasClass
|
import kotlin.dom.hasClass
|
||||||
|
|
||||||
external val module: Module
|
external val module: Module
|
||||||
|
|
||||||
external interface Module {
|
|
||||||
val hot: Hot?
|
|
||||||
}
|
|
||||||
|
|
||||||
external interface Hot {
|
external interface Hot {
|
||||||
val data: dynamic
|
val data: dynamic
|
||||||
|
|
||||||
@ -19,17 +15,31 @@ external interface Hot {
|
|||||||
fun dispose(callback: (data: dynamic) -> Unit)
|
fun dispose(callback: (data: dynamic) -> Unit)
|
||||||
}
|
}
|
||||||
|
|
||||||
external fun require(name: String): dynamic
|
external interface Module {
|
||||||
|
val hot: Hot?
|
||||||
abstract class ApplicationBase {
|
|
||||||
open val stateKeys: List<String> get() = emptyList()
|
|
||||||
|
|
||||||
abstract fun start(state: Map<String, Any>)
|
|
||||||
open fun dispose(): Map<String, Any> = emptyMap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startApplication(builder: () -> ApplicationBase) {
|
/**
|
||||||
fun start(state: dynamic): ApplicationBase? {
|
* Base interface for applications.
|
||||||
|
*
|
||||||
|
* Base interface for applications supporting Hot Module Replacement (HMR).
|
||||||
|
*/
|
||||||
|
interface Application {
|
||||||
|
/**
|
||||||
|
* Starting point for an application.
|
||||||
|
* @param state Initial state between Hot Module Replacement (HMR).
|
||||||
|
*/
|
||||||
|
fun start(state: Map<String, Any>)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ending point for an application.
|
||||||
|
* @return final state for Hot Module Replacement (HMR).
|
||||||
|
*/
|
||||||
|
fun dispose(): Map<String, Any> = emptyMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startApplication(builder: () -> Application) {
|
||||||
|
fun start(state: dynamic): Application? {
|
||||||
return if (document.body?.hasClass("testApp") == true) {
|
return if (document.body?.hasClass("testApp") == true) {
|
||||||
val application = builder()
|
val application = builder()
|
||||||
|
|
||||||
@ -42,7 +52,7 @@ fun startApplication(builder: () -> ApplicationBase) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var application: ApplicationBase? = null
|
var application: Application? = null
|
||||||
|
|
||||||
val state: dynamic = module.hot?.let { hot ->
|
val state: dynamic = module.hot?.let { hot ->
|
||||||
hot.accept()
|
hot.accept()
|
@ -0,0 +1,24 @@
|
|||||||
|
package hep.dataforge.js
|
||||||
|
|
||||||
|
@JsName("require")
|
||||||
|
external fun requireJS(name: String): dynamic
|
||||||
|
|
||||||
|
inline fun <T : Any> jsObject(builder: T.() -> Unit): T {
|
||||||
|
val obj: T = js("({})") as T
|
||||||
|
return obj.apply {
|
||||||
|
builder()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun js(builder: dynamic.() -> Unit): dynamic = jsObject(builder)
|
||||||
|
|
||||||
|
//fun <T : Any> clone(obj: T) = objectAssign(jsObject<T> {}, obj)
|
||||||
|
|
||||||
|
//inline fun <T : Any> assign(obj: T, builder: T.() -> Unit) = clone(obj).apply(builder)
|
||||||
|
|
||||||
|
fun toPlainObjectStripNull(obj: Any) = js {
|
||||||
|
for (key in Object.keys(obj)) {
|
||||||
|
val value = obj.asDynamic()[key]
|
||||||
|
if (value != null) this[key] = value
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package hep.dataforge.vis.js.editor
|
||||||
|
|
||||||
|
import kotlinx.html.TagConsumer
|
||||||
|
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) {
|
||||||
|
div("card w-100") {
|
||||||
|
div("card-body") {
|
||||||
|
h3(classes = "card-title") { +title }
|
||||||
|
block()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,78 @@
|
|||||||
|
package hep.dataforge.vis.js.editor
|
||||||
|
|
||||||
|
import hep.dataforge.names.Name
|
||||||
|
import hep.dataforge.names.plus
|
||||||
|
import hep.dataforge.vis.common.VisualGroup
|
||||||
|
import hep.dataforge.vis.common.VisualObject
|
||||||
|
import hep.dataforge.vis.common.isEmpty
|
||||||
|
import kotlinx.html.TagConsumer
|
||||||
|
import kotlinx.html.dom.append
|
||||||
|
import kotlinx.html.js.*
|
||||||
|
import org.w3c.dom.Element
|
||||||
|
import org.w3c.dom.HTMLElement
|
||||||
|
import org.w3c.dom.HTMLSpanElement
|
||||||
|
import kotlin.dom.clear
|
||||||
|
|
||||||
|
fun Element.displayObjectTree(
|
||||||
|
obj: VisualObject,
|
||||||
|
clickCallback: (Name) -> Unit = {}
|
||||||
|
) {
|
||||||
|
clear()
|
||||||
|
append {
|
||||||
|
card("Object tree") {
|
||||||
|
subTree(Name.EMPTY, obj, clickCallback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun TagConsumer<HTMLElement>.subTree(
|
||||||
|
fullName: Name,
|
||||||
|
obj: VisualObject,
|
||||||
|
clickCallback: (Name) -> Unit
|
||||||
|
) {
|
||||||
|
// val fullName = parentName + token
|
||||||
|
val token = fullName.last()?.toString()?:"World"
|
||||||
|
|
||||||
|
//display as node if any child is visible
|
||||||
|
if (obj is VisualGroup && obj.children.keys.any { !it.body.startsWith("@") }) {
|
||||||
|
lateinit var toggle: HTMLSpanElement
|
||||||
|
div("d-inline-block text-truncate") {
|
||||||
|
toggle = span("objTree-caret")
|
||||||
|
label("objTree-label") {
|
||||||
|
+token
|
||||||
|
onClickFunction = { clickCallback(fullName) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val subtree = ul("objTree-subtree")
|
||||||
|
toggle.onclick = {
|
||||||
|
toggle.classList.toggle("objTree-caret-down")
|
||||||
|
subtree.apply {
|
||||||
|
//If expanded, add children dynamically
|
||||||
|
if (toggle.classList.contains("objTree-caret-down")) {
|
||||||
|
obj.children.entries
|
||||||
|
.filter { !it.key.toString().startsWith("@") } // ignore statics and other hidden children
|
||||||
|
.sortedBy { (it.value as? VisualGroup)?.isEmpty ?: true }
|
||||||
|
.forEach { (childToken, child) ->
|
||||||
|
append {
|
||||||
|
li().apply {
|
||||||
|
subTree(fullName + childToken, child, clickCallback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// if not, clear them to conserve memory on very long lists
|
||||||
|
this.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
div("d-inline-block text-truncate") {
|
||||||
|
span("objTree-leaf")
|
||||||
|
label("objTree-label") {
|
||||||
|
+token
|
||||||
|
onClickFunction = { clickCallback(fullName) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,185 @@
|
|||||||
|
@file:Suppress(
|
||||||
|
"INTERFACE_WITH_SUPERCLASS",
|
||||||
|
"OVERRIDING_FINAL_MEMBER",
|
||||||
|
"RETURN_TYPE_MISMATCH_ON_OVERRIDE",
|
||||||
|
"CONFLICTING_OVERLOADS",
|
||||||
|
"EXTERNAL_DELEGATION"
|
||||||
|
)
|
||||||
|
|
||||||
|
package hep.dataforge.vis.js.editor
|
||||||
|
|
||||||
|
import org.w3c.dom.HTMLElement
|
||||||
|
|
||||||
|
external interface Node {
|
||||||
|
var field: String
|
||||||
|
var value: String? get() = definedExternally; set(value) = definedExternally
|
||||||
|
var path: dynamic
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface NodeName {
|
||||||
|
var path: Array<String>
|
||||||
|
var type: dynamic /* 'object' | 'array' */
|
||||||
|
var size: Number
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface ValidationError {
|
||||||
|
var path: dynamic
|
||||||
|
var message: String
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface Template {
|
||||||
|
var text: String
|
||||||
|
var title: String
|
||||||
|
var className: String? get() = definedExternally; set(value) = definedExternally
|
||||||
|
var field: String
|
||||||
|
var value: Any
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface `T$6` {
|
||||||
|
var startFrom: Number
|
||||||
|
var options: Array<String>
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface AutoCompleteOptions {
|
||||||
|
var confirmKeys: Array<Number>? get() = definedExternally; set(value) = definedExternally
|
||||||
|
var caseSensitive: Boolean? get() = definedExternally; set(value) = definedExternally
|
||||||
|
// var getOptions: AutoCompleteOptionsGetter? get() = definedExternally; set(value) = definedExternally
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface SelectionPosition {
|
||||||
|
var row: Number
|
||||||
|
var column: Number
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface SerializableNode {
|
||||||
|
var value: Any
|
||||||
|
var path: dynamic
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface Color {
|
||||||
|
var rgba: Array<Number>
|
||||||
|
var hsla: Array<Number>
|
||||||
|
var rgbString: String
|
||||||
|
var rgbaString: String
|
||||||
|
var hslString: String
|
||||||
|
var hslaString: String
|
||||||
|
var hex: String
|
||||||
|
}
|
||||||
|
|
||||||
|
//external interface `T$0` {
|
||||||
|
// var field: Boolean
|
||||||
|
// var value: Boolean
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//external interface `T$1` {
|
||||||
|
// @nativeGetter
|
||||||
|
// operator fun get(key: String): String?
|
||||||
|
//
|
||||||
|
// @nativeSetter
|
||||||
|
// operator fun set(key: String, value: String)
|
||||||
|
//}
|
||||||
|
|
||||||
|
//external interface Languages {
|
||||||
|
// @nativeGetter
|
||||||
|
// operator fun get(lang: String): `T$1`?
|
||||||
|
//
|
||||||
|
// @nativeSetter
|
||||||
|
// operator fun set(lang: String, value: `T$1`)
|
||||||
|
//}
|
||||||
|
|
||||||
|
external interface JSONEditorOptions {
|
||||||
|
// var ace: AceAjax.Ace? get() = definedExternally; set(value) = definedExternally
|
||||||
|
// var ajv: Ajv? get() = definedExternally; set(value) = definedExternally
|
||||||
|
var onChange: (() -> Unit)? get() = definedExternally; set(value) = definedExternally
|
||||||
|
var onChangeJSON: ((json: Any) -> Unit)? get() = definedExternally; set(value) = definedExternally
|
||||||
|
var onChangeText: ((jsonString: String) -> Unit)? get() = definedExternally; set(value) = definedExternally
|
||||||
|
var onEditable: ((node: Node) -> dynamic)? get() = definedExternally; set(value) = definedExternally
|
||||||
|
var onError: ((error: Error) -> Unit)? get() = definedExternally; set(value) = definedExternally
|
||||||
|
var onModeChange: ((newMode: dynamic /* 'tree' | 'view' | 'form' | 'code' | 'text' */, oldMode: dynamic /* 'tree' | 'view' | 'form' | 'code' | 'text' */) -> Unit)? get() = definedExternally; set(value) = definedExternally
|
||||||
|
var onNodeName: ((nodeName: NodeName) -> String?)? get() = definedExternally; set(value) = definedExternally
|
||||||
|
var onValidate: ((json: Any) -> dynamic)? get() = definedExternally; set(value) = definedExternally
|
||||||
|
var escapeUnicode: Boolean? get() = definedExternally; set(value) = definedExternally
|
||||||
|
var sortObjectKeys: Boolean? get() = definedExternally; set(value) = definedExternally
|
||||||
|
var history: Boolean? get() = definedExternally; set(value) = definedExternally
|
||||||
|
var mode: dynamic /* 'tree' | 'view' | 'form' | 'code' | 'text' */
|
||||||
|
var modes: Array<dynamic /* 'tree' | 'view' | 'form' | 'code' | 'text' */>? get() = definedExternally; set(value) = definedExternally
|
||||||
|
var name: String? get() = definedExternally; set(value) = definedExternally
|
||||||
|
var schema: Any? get() = definedExternally; set(value) = definedExternally
|
||||||
|
var schemaRefs: Any? get() = definedExternally; set(value) = definedExternally
|
||||||
|
var search: Boolean? get() = definedExternally; set(value) = definedExternally
|
||||||
|
var indentation: Number? get() = definedExternally; set(value) = definedExternally
|
||||||
|
var theme: String? get() = definedExternally; set(value) = definedExternally
|
||||||
|
var templates: Array<Template>? get() = definedExternally; set(value) = definedExternally
|
||||||
|
var autocomplete: AutoCompleteOptions? get() = definedExternally; set(value) = definedExternally
|
||||||
|
var mainMenuBar: Boolean? get() = definedExternally; set(value) = definedExternally
|
||||||
|
var navigationBar: Boolean? get() = definedExternally; set(value) = definedExternally
|
||||||
|
var statusBar: Boolean? get() = definedExternally; set(value) = definedExternally
|
||||||
|
var onTextSelectionChange: ((start: SelectionPosition, end: SelectionPosition, text: String) -> Unit)? get() = definedExternally; set(value) = definedExternally
|
||||||
|
var onSelectionChange: ((start: SerializableNode, end: SerializableNode) -> Unit)? get() = definedExternally; set(value) = definedExternally
|
||||||
|
var onEvent: ((node: Node, event: String) -> Unit)? get() = definedExternally; set(value) = definedExternally
|
||||||
|
var colorPicker: Boolean? get() = definedExternally; set(value) = definedExternally
|
||||||
|
var onColorPicker: ((parent: HTMLElement, color: String, onChange: (color: Color) -> Unit) -> Unit)? get() = definedExternally; set(value) = definedExternally
|
||||||
|
var timestampTag: Boolean? get() = definedExternally; set(value) = definedExternally
|
||||||
|
var language: String? get() = definedExternally; set(value) = definedExternally
|
||||||
|
//var languages: Languages? get() = definedExternally; set(value) = definedExternally
|
||||||
|
var modalAnchor: HTMLElement? get() = definedExternally; set(value) = definedExternally
|
||||||
|
var enableSort: Boolean? get() = definedExternally; set(value) = definedExternally
|
||||||
|
var enableTransform: Boolean? get() = definedExternally; set(value) = definedExternally
|
||||||
|
var maxVisibleChilds: Number? get() = definedExternally; set(value) = definedExternally
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface JsonPath {
|
||||||
|
var path: dynamic
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface EditorSelection {
|
||||||
|
var start: SerializableNode
|
||||||
|
var end: SerializableNode
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface TextSelection {
|
||||||
|
var start: SelectionPosition
|
||||||
|
var end: SelectionPosition
|
||||||
|
var text: String
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsModule("jsoneditor")
|
||||||
|
@JsNonModule
|
||||||
|
external open class JSONEditor(
|
||||||
|
container: HTMLElement,
|
||||||
|
options: JSONEditorOptions? = definedExternally /* null */,
|
||||||
|
json: dynamic = definedExternally /* null */
|
||||||
|
) {
|
||||||
|
open fun collapseAll()
|
||||||
|
open fun destroy()
|
||||||
|
open fun expandAll()
|
||||||
|
open fun focus()
|
||||||
|
open fun get(): Any
|
||||||
|
open fun getMode(): dynamic /* 'tree' | 'view' | 'form' | 'code' | 'text' */
|
||||||
|
open fun getName(): String?
|
||||||
|
open fun getNodesByRange(start: JsonPath, end: JsonPath): Array<SerializableNode>
|
||||||
|
open fun getSelection(): EditorSelection
|
||||||
|
open fun getText(): String
|
||||||
|
open fun getTextSelection(): TextSelection
|
||||||
|
open fun refresh()
|
||||||
|
open fun set(json: Any)
|
||||||
|
open fun setMode(mode: String /* 'tree' */)
|
||||||
|
open fun setMode(mode: String /* 'view' */)
|
||||||
|
open fun setMode(mode: String /* 'form' */)
|
||||||
|
open fun setMode(mode: String /* 'code' */)
|
||||||
|
open fun setMode(mode: String /* 'text' */)
|
||||||
|
open fun setName(name: String? = definedExternally /* null */)
|
||||||
|
open fun setSchema(schema: Any?, schemaRefs: Any? = definedExternally /* null */)
|
||||||
|
open fun setSelection(start: JsonPath, end: JsonPath)
|
||||||
|
open fun setText(jsonString: String)
|
||||||
|
open fun setTextSelection(start: SelectionPosition, end: SelectionPosition)
|
||||||
|
open fun update(json: Any)
|
||||||
|
open fun updateText(jsonString: String)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
var VALID_OPTIONS: Array<String>
|
||||||
|
// var ace: AceAjax.Ace
|
||||||
|
// var Ajv: Ajv
|
||||||
|
var VanillaPicker: Any
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
package hep.dataforge.vis.js.editor
|
||||||
|
|
||||||
|
import hep.dataforge.io.toJson
|
||||||
|
import hep.dataforge.js.jsObject
|
||||||
|
import hep.dataforge.meta.DynamicMeta
|
||||||
|
import hep.dataforge.meta.Meta
|
||||||
|
import hep.dataforge.meta.update
|
||||||
|
import hep.dataforge.names.Name
|
||||||
|
import hep.dataforge.names.isEmpty
|
||||||
|
import hep.dataforge.vis.common.VisualObject
|
||||||
|
import hep.dataforge.vis.common.findStyle
|
||||||
|
import kotlinx.html.dom.append
|
||||||
|
import kotlinx.html.js.*
|
||||||
|
import org.w3c.dom.Element
|
||||||
|
import kotlin.collections.forEach
|
||||||
|
import kotlin.collections.isNotEmpty
|
||||||
|
import kotlin.collections.set
|
||||||
|
import kotlin.dom.clear
|
||||||
|
|
||||||
|
//FIXME something rotten in JS-Meta converter
|
||||||
|
fun Meta.toDynamic() = JSON.parse<dynamic>(toJson().toString())
|
||||||
|
|
||||||
|
//TODO add node descriptor instead of configuring property selector
|
||||||
|
fun Element.displayPropertyEditor(
|
||||||
|
name: Name,
|
||||||
|
item: VisualObject,
|
||||||
|
propertySelector: (VisualObject) -> Meta = { it.config }
|
||||||
|
) {
|
||||||
|
clear()
|
||||||
|
|
||||||
|
append {
|
||||||
|
card("Properties") {
|
||||||
|
if (!name.isEmpty()) {
|
||||||
|
nav {
|
||||||
|
attributes["aria-label"] = "breadcrumb"
|
||||||
|
ol("breadcrumb") {
|
||||||
|
name.tokens.forEach { token ->
|
||||||
|
li("breadcrumb-item") {
|
||||||
|
+token.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val dMeta: dynamic = propertySelector(item).toDynamic()
|
||||||
|
val options: JSONEditorOptions = jsObject {
|
||||||
|
mode = "form"
|
||||||
|
onChangeJSON = { item.config.update(DynamicMeta(it.asDynamic())) }
|
||||||
|
}
|
||||||
|
JSONEditor(div(), options, dMeta)
|
||||||
|
}
|
||||||
|
|
||||||
|
val styles = item.styles
|
||||||
|
if (styles.isNotEmpty()) {
|
||||||
|
card("Styles") {
|
||||||
|
item.styles.forEach { style ->
|
||||||
|
val styleMeta = item.findStyle(style)
|
||||||
|
h4("container") { +style }
|
||||||
|
if (styleMeta != null) {
|
||||||
|
div("container").apply {
|
||||||
|
val options: JSONEditorOptions = jsObject {
|
||||||
|
mode = "view"
|
||||||
|
}
|
||||||
|
JSONEditor(
|
||||||
|
this,
|
||||||
|
options,
|
||||||
|
styleMeta.toDynamic()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
23
dataforge-vis-common/src/jsMain/resources/css/common.css
Normal file
23
dataforge-vis-common/src/jsMain/resources/css/common.css
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/* Remove default bullets */
|
||||||
|
ul, .objTree-subtree {
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style the caret/arrow */
|
||||||
|
.objTree-caret {
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none; /* Prevent text selection */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create the caret/arrow with a unicode, and style it */
|
||||||
|
.objTree-caret::before {
|
||||||
|
content: "\25B6";
|
||||||
|
color: black;
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Rotate the caret/arrow icon when clicked on (using JavaScript) */
|
||||||
|
.objTree-caret-down::before {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
@ -96,7 +96,7 @@ class FXPlugin(meta: Meta = EmptyMeta) : AbstractPlugin(meta) {
|
|||||||
companion object : PluginFactory<FXPlugin> {
|
companion object : PluginFactory<FXPlugin> {
|
||||||
override val type: KClass<out FXPlugin> = FXPlugin::class
|
override val type: KClass<out FXPlugin> = FXPlugin::class
|
||||||
override val tag: PluginTag = PluginTag("vis.fx", group = PluginTag.DATAFORGE_GROUP)
|
override val tag: PluginTag = PluginTag("vis.fx", group = PluginTag.DATAFORGE_GROUP)
|
||||||
override fun invoke(meta: Meta): FXPlugin = FXPlugin(meta)
|
override fun invoke(meta: Meta, context: Context): FXPlugin = FXPlugin(meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,6 +1,8 @@
|
|||||||
package hep.dataforge.vis.fx.values
|
package hep.dataforge.vis.fx.editor
|
||||||
|
|
||||||
import hep.dataforge.meta.Meta
|
import hep.dataforge.meta.Meta
|
||||||
|
import hep.dataforge.names.Name
|
||||||
|
import hep.dataforge.names.asName
|
||||||
import hep.dataforge.values.Null
|
import hep.dataforge.values.Null
|
||||||
import hep.dataforge.values.Value
|
import hep.dataforge.values.Value
|
||||||
import hep.dataforge.values.asValue
|
import hep.dataforge.values.asValue
|
||||||
@ -39,9 +41,10 @@ class ColorValueChooser : ValueChooserBase<ColorPicker>() {
|
|||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object: ValueChooser.Factory{
|
companion object : ValueChooser.Factory {
|
||||||
override val name: String = "color"
|
override val name: Name = "color".asName()
|
||||||
|
|
||||||
override fun invoke(meta: Meta): ValueChooser = ColorValueChooser()
|
override fun invoke(meta: Meta): ValueChooser =
|
||||||
|
ColorValueChooser()
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -3,11 +3,13 @@
|
|||||||
* To change this template file, choose Tools | Templates
|
* To change this template file, choose Tools | Templates
|
||||||
* and open the template in the editor.
|
* and open the template in the editor.
|
||||||
*/
|
*/
|
||||||
package hep.dataforge.vis.fx.values
|
package hep.dataforge.vis.fx.editor
|
||||||
|
|
||||||
import hep.dataforge.meta.Meta
|
import hep.dataforge.meta.Meta
|
||||||
import hep.dataforge.meta.get
|
import hep.dataforge.meta.get
|
||||||
import hep.dataforge.meta.value
|
import hep.dataforge.meta.value
|
||||||
|
import hep.dataforge.names.Name
|
||||||
|
import hep.dataforge.names.asName
|
||||||
import hep.dataforge.values.Value
|
import hep.dataforge.values.Value
|
||||||
import hep.dataforge.values.parseValue
|
import hep.dataforge.values.parseValue
|
||||||
import javafx.collections.FXCollections
|
import javafx.collections.FXCollections
|
||||||
@ -50,9 +52,10 @@ class ComboBoxValueChooser(val values: Collection<Value>? = null) : ValueChooser
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object : ValueChooser.Factory {
|
companion object : ValueChooser.Factory {
|
||||||
override val name: String = "combo"
|
override val name: Name = "combo".asName()
|
||||||
|
|
||||||
override fun invoke(meta: Meta): ValueChooser = ComboBoxValueChooser(meta["values"].value?.list)
|
override fun invoke(meta: Meta): ValueChooser =
|
||||||
|
ComboBoxValueChooser(meta["values"].value?.list)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -3,20 +3,22 @@
|
|||||||
* To change this template file, choose Tools | Templates
|
* To change this template file, choose Tools | Templates
|
||||||
* and open the template in the editor.
|
* and open the template in the editor.
|
||||||
*/
|
*/
|
||||||
package hep.dataforge.vis.fx.meta
|
package hep.dataforge.vis.fx.editor
|
||||||
|
|
||||||
|
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon
|
||||||
|
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIconView
|
||||||
import hep.dataforge.context.Global
|
import hep.dataforge.context.Global
|
||||||
import hep.dataforge.descriptors.NodeDescriptor
|
import hep.dataforge.descriptors.NodeDescriptor
|
||||||
import hep.dataforge.meta.Config
|
import hep.dataforge.meta.Config
|
||||||
import hep.dataforge.names.NameToken
|
import hep.dataforge.names.NameToken
|
||||||
import hep.dataforge.vis.fx.dfIconView
|
import hep.dataforge.vis.fx.dfIconView
|
||||||
import hep.dataforge.vis.fx.values.ValueChooser
|
import javafx.scene.Node
|
||||||
import javafx.scene.control.*
|
import javafx.scene.control.*
|
||||||
import javafx.scene.control.cell.TextFieldTreeTableCell
|
import javafx.scene.control.cell.TextFieldTreeTableCell
|
||||||
|
import javafx.scene.layout.HBox
|
||||||
import javafx.scene.layout.Priority
|
import javafx.scene.layout.Priority
|
||||||
import javafx.scene.paint.Color
|
import javafx.scene.paint.Color
|
||||||
import javafx.scene.text.Text
|
import javafx.scene.text.Text
|
||||||
import org.controlsfx.glyphfont.Glyph
|
|
||||||
import tornadofx.*
|
import tornadofx.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -29,8 +31,9 @@ class ConfigEditor(
|
|||||||
val allowNew: Boolean = true,
|
val allowNew: Boolean = true,
|
||||||
title: String = "Configuration editor"
|
title: String = "Configuration editor"
|
||||||
) : Fragment(title = title, icon = dfIconView) {
|
) : Fragment(title = title, icon = dfIconView) {
|
||||||
|
//TODO replace parameters by properties
|
||||||
|
|
||||||
constructor(config: Config, descriptor: NodeDescriptor, title: String = "Configuration editor") :
|
constructor(config: Config, descriptor: NodeDescriptor?, title: String = "Configuration editor") :
|
||||||
this(FXMeta.root(config, descriptor = descriptor), title = title)
|
this(FXMeta.root(config, descriptor = descriptor), title = title)
|
||||||
|
|
||||||
override val root = borderpane {
|
override val root = borderpane {
|
||||||
@ -133,8 +136,9 @@ class ConfigEditor(
|
|||||||
is FXMetaNode<Config> -> {
|
is FXMetaNode<Config> -> {
|
||||||
if (allowNew) {
|
if (allowNew) {
|
||||||
text = null
|
text = null
|
||||||
graphic = hbox {
|
graphic = HBox().apply {
|
||||||
button("node", Glyph("FontAwesome", "PLUS_CIRCLE")) {
|
val glyph: Node = FontAwesomeIconView(FontAwesomeIcon.PLUS_CIRCLE)
|
||||||
|
button("node", graphic = glyph) {
|
||||||
hgrow = Priority.ALWAYS
|
hgrow = Priority.ALWAYS
|
||||||
maxWidth = Double.POSITIVE_INFINITY
|
maxWidth = Double.POSITIVE_INFINITY
|
||||||
action {
|
action {
|
||||||
@ -143,7 +147,7 @@ class ConfigEditor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
button("value", Glyph("FontAwesome", "PLUS_SQUARE")) {
|
button("value", graphic = FontAwesomeIconView(FontAwesomeIcon.PLUS_SQUARE)) {
|
||||||
hgrow = Priority.ALWAYS
|
hgrow = Priority.ALWAYS
|
||||||
maxWidth = Double.POSITIVE_INFINITY
|
maxWidth = Double.POSITIVE_INFINITY
|
||||||
action {
|
action {
|
@ -1,4 +1,4 @@
|
|||||||
package hep.dataforge.vis.fx.meta
|
package hep.dataforge.vis.fx.editor
|
||||||
|
|
||||||
import hep.dataforge.descriptors.ItemDescriptor
|
import hep.dataforge.descriptors.ItemDescriptor
|
||||||
import hep.dataforge.descriptors.NodeDescriptor
|
import hep.dataforge.descriptors.NodeDescriptor
|
||||||
@ -84,7 +84,7 @@ class FXMetaNode<M : MetaNode<M>>(
|
|||||||
override val hasValue: ObservableBooleanValue = nodeProperty.booleanBinding { it != null }
|
override val hasValue: ObservableBooleanValue = nodeProperty.booleanBinding { it != null }
|
||||||
|
|
||||||
private val filter: (FXMeta<M>) -> Boolean = { cfg ->
|
private val filter: (FXMeta<M>) -> Boolean = { cfg ->
|
||||||
!(cfg.descriptor?.tags?.contains(ConfigEditor.NO_CONFIGURATOR_TAG) ?: false)
|
!(cfg.descriptor?.attributes?.get(ConfigEditor.NO_CONFIGURATOR_TAG)?.boolean ?: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
val children = object : ListBinding<FXMeta<M>>() {
|
val children = object : ListBinding<FXMeta<M>>() {
|
||||||
@ -172,7 +172,7 @@ fun <M : MutableMeta<M>> FXMetaNode<M>.remove(name: NameToken) {
|
|||||||
private fun <M : MutableMeta<M>> M.createEmptyNode(token: NameToken, append: Boolean): M {
|
private fun <M : MutableMeta<M>> M.createEmptyNode(token: NameToken, append: Boolean): M {
|
||||||
return if (append && token.index.isNotEmpty()) {
|
return if (append && token.index.isNotEmpty()) {
|
||||||
val name = token.asName()
|
val name = token.asName()
|
||||||
val index = (getAll(name).keys.mapNotNull { it.toIntOrNull() }.max() ?: -1) + 1
|
val index = (getIndexed(name).keys.mapNotNull { it.toIntOrNull() }.max() ?: -1) + 1
|
||||||
val newName = name.withIndex(index.toString())
|
val newName = name.withIndex(index.toString())
|
||||||
set(newName, EmptyMeta)
|
set(newName, EmptyMeta)
|
||||||
get(newName).node!!
|
get(newName).node!!
|
@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package hep.dataforge.vis.fx.meta
|
package hep.dataforge.vis.fx.editor
|
||||||
|
|
||||||
import hep.dataforge.meta.Meta
|
import hep.dataforge.meta.Meta
|
||||||
import hep.dataforge.vis.fx.dfIconView
|
import hep.dataforge.vis.fx.dfIconView
|
@ -3,9 +3,11 @@
|
|||||||
* To change this template file, choose Tools | Templates
|
* To change this template file, choose Tools | Templates
|
||||||
* and open the template in the editor.
|
* and open the template in the editor.
|
||||||
*/
|
*/
|
||||||
package hep.dataforge.vis.fx.values
|
package hep.dataforge.vis.fx.editor
|
||||||
|
|
||||||
import hep.dataforge.meta.Meta
|
import hep.dataforge.meta.Meta
|
||||||
|
import hep.dataforge.names.Name
|
||||||
|
import hep.dataforge.names.asName
|
||||||
import hep.dataforge.values.*
|
import hep.dataforge.values.*
|
||||||
import javafx.beans.value.ObservableValue
|
import javafx.beans.value.ObservableValue
|
||||||
import javafx.scene.control.TextField
|
import javafx.scene.control.TextField
|
||||||
@ -100,7 +102,8 @@ class TextValueChooser : ValueChooserBase<TextField>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object : ValueChooser.Factory {
|
companion object : ValueChooser.Factory {
|
||||||
override val name: String = "text"
|
override val name: Name = "text".asName()
|
||||||
override fun invoke(meta: Meta): ValueChooser = TextValueChooser()
|
override fun invoke(meta: Meta): ValueChooser =
|
||||||
|
TextValueChooser()
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -3,7 +3,7 @@
|
|||||||
* To change this template file, choose Tools | Templates
|
* To change this template file, choose Tools | Templates
|
||||||
* and open the template in the editor.
|
* and open the template in the editor.
|
||||||
*/
|
*/
|
||||||
package hep.dataforge.vis.fx.values
|
package hep.dataforge.vis.fx.editor
|
||||||
|
|
||||||
import hep.dataforge.values.Value
|
import hep.dataforge.values.Value
|
||||||
|
|
@ -3,13 +3,14 @@
|
|||||||
* To change this template file, choose Tools | Templates
|
* To change this template file, choose Tools | Templates
|
||||||
* and open the template in the editor.
|
* and open the template in the editor.
|
||||||
*/
|
*/
|
||||||
package hep.dataforge.vis.fx.values
|
package hep.dataforge.vis.fx.editor
|
||||||
|
|
||||||
import hep.dataforge.context.Context
|
import hep.dataforge.context.Context
|
||||||
import hep.dataforge.context.Named
|
import hep.dataforge.context.Named
|
||||||
import hep.dataforge.descriptors.ValueDescriptor
|
import hep.dataforge.descriptors.ValueDescriptor
|
||||||
import hep.dataforge.meta.EmptyMeta
|
import hep.dataforge.meta.EmptyMeta
|
||||||
import hep.dataforge.meta.Meta
|
import hep.dataforge.meta.Meta
|
||||||
|
import hep.dataforge.names.toName
|
||||||
import hep.dataforge.provider.Type
|
import hep.dataforge.provider.Type
|
||||||
import hep.dataforge.provider.provideByType
|
import hep.dataforge.provider.provideByType
|
||||||
import hep.dataforge.values.Null
|
import hep.dataforge.values.Null
|
||||||
@ -71,7 +72,7 @@ interface ValueChooser {
|
|||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private fun findWidgetByType(context: Context, type: String): Factory? {
|
private fun findWidgetByType(context: Context, type: String): Factory? {
|
||||||
return when (type) {
|
return when (type.toName()) {
|
||||||
TextValueChooser.name -> TextValueChooser
|
TextValueChooser.name -> TextValueChooser
|
||||||
ColorValueChooser.name -> ColorValueChooser
|
ColorValueChooser.name -> ColorValueChooser
|
||||||
ComboBoxValueChooser.name -> ComboBoxValueChooser
|
ComboBoxValueChooser.name -> ComboBoxValueChooser
|
||||||
@ -86,7 +87,10 @@ interface ValueChooser {
|
|||||||
val widgetType = descriptor.widgetType
|
val widgetType = descriptor.widgetType
|
||||||
val chooser: ValueChooser = when {
|
val chooser: ValueChooser = when {
|
||||||
widgetType != null -> {
|
widgetType != null -> {
|
||||||
findWidgetByType(context, widgetType)?.invoke(
|
findWidgetByType(
|
||||||
|
context,
|
||||||
|
widgetType
|
||||||
|
)?.invoke(
|
||||||
descriptor.widget
|
descriptor.widget
|
||||||
) ?: TextValueChooser()
|
) ?: TextValueChooser()
|
||||||
}
|
}
|
||||||
@ -104,7 +108,8 @@ interface ValueChooser {
|
|||||||
descriptor: ValueDescriptor? = null,
|
descriptor: ValueDescriptor? = null,
|
||||||
setter: (Value) -> Unit
|
setter: (Value) -> Unit
|
||||||
): ValueChooser {
|
): ValueChooser {
|
||||||
val chooser = build(context, descriptor)
|
val chooser =
|
||||||
|
build(context, descriptor)
|
||||||
chooser.setDisplayValue(value.value ?: Null)
|
chooser.setDisplayValue(value.value ?: Null)
|
||||||
value.onChange {
|
value.onChange {
|
||||||
chooser.setDisplayValue(it ?: Null)
|
chooser.setDisplayValue(it ?: Null)
|
||||||
@ -114,7 +119,11 @@ interface ValueChooser {
|
|||||||
setter(result)
|
setter(result)
|
||||||
ValueCallbackResponse(true, result, "OK")
|
ValueCallbackResponse(true, result, "OK")
|
||||||
} else {
|
} else {
|
||||||
ValueCallbackResponse(false, value.value ?: Null, "Not allowed")
|
ValueCallbackResponse(
|
||||||
|
false,
|
||||||
|
value.value ?: Null,
|
||||||
|
"Not allowed"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return chooser
|
return chooser
|
@ -3,7 +3,7 @@
|
|||||||
* To change this template file, choose Tools | Templates
|
* To change this template file, choose Tools | Templates
|
||||||
* and open the template in the editor.
|
* and open the template in the editor.
|
||||||
*/
|
*/
|
||||||
package hep.dataforge.vis.fx.values
|
package hep.dataforge.vis.fx.editor
|
||||||
|
|
||||||
import hep.dataforge.descriptors.ValueDescriptor
|
import hep.dataforge.descriptors.ValueDescriptor
|
||||||
import hep.dataforge.values.Null
|
import hep.dataforge.values.Null
|
@ -0,0 +1,74 @@
|
|||||||
|
package hep.dataforge.vis.fx.editor
|
||||||
|
|
||||||
|
import hep.dataforge.descriptors.NodeDescriptor
|
||||||
|
import hep.dataforge.meta.Config
|
||||||
|
import hep.dataforge.meta.Meta
|
||||||
|
import hep.dataforge.meta.update
|
||||||
|
import hep.dataforge.vis.common.VisualObject
|
||||||
|
import hep.dataforge.vis.common.findStyle
|
||||||
|
import javafx.beans.binding.Binding
|
||||||
|
import javafx.beans.property.SimpleObjectProperty
|
||||||
|
import javafx.scene.Node
|
||||||
|
import javafx.scene.Parent
|
||||||
|
import javafx.scene.layout.VBox
|
||||||
|
import tornadofx.*
|
||||||
|
|
||||||
|
class VisualObjectEditorFragment(val selector: (VisualObject) -> Meta) : Fragment() {
|
||||||
|
|
||||||
|
val itemProperty = SimpleObjectProperty<VisualObject>()
|
||||||
|
var item: VisualObject? by itemProperty
|
||||||
|
val descriptorProperty = SimpleObjectProperty<NodeDescriptor>()
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
item: VisualObject?,
|
||||||
|
descriptor: NodeDescriptor?,
|
||||||
|
selector: (VisualObject) -> Config = { it.config }
|
||||||
|
) : this(selector) {
|
||||||
|
this.item = item
|
||||||
|
this.descriptorProperty.set(descriptor)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var currentConfig: Config? = null
|
||||||
|
|
||||||
|
private val configProperty: Binding<Config?> = itemProperty.objectBinding { visualObject ->
|
||||||
|
if (visualObject == null) return@objectBinding null
|
||||||
|
val meta = selector(visualObject)
|
||||||
|
val config = Config().apply {
|
||||||
|
update(meta)
|
||||||
|
onChange(this@VisualObjectEditorFragment) { key, _, after ->
|
||||||
|
visualObject.setProperty(key, after)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//remember old config reference to cleanup listeners
|
||||||
|
currentConfig?.removeListener(this)
|
||||||
|
currentConfig = config
|
||||||
|
config
|
||||||
|
}
|
||||||
|
|
||||||
|
private val configEditorProperty: Binding<Node?> = configProperty.objectBinding(descriptorProperty) {
|
||||||
|
it?.let {
|
||||||
|
ConfigEditor(it, descriptorProperty.get()).root
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val styleBoxProperty: Binding<Node?> = configProperty.objectBinding() {
|
||||||
|
VBox().apply {
|
||||||
|
item?.styles?.forEach { styleName ->
|
||||||
|
val styleMeta = item?.findStyle(styleName)
|
||||||
|
if (styleMeta != null) {
|
||||||
|
titledpane(styleName, node = MetaViewer(styleMeta).root)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val root: Parent = vbox {
|
||||||
|
titledpane("Properties", collapsible = false) {
|
||||||
|
contentProperty().bind(configEditorProperty)
|
||||||
|
}
|
||||||
|
titledpane("Styles", collapsible = false) {
|
||||||
|
visibleWhen(itemProperty.booleanBinding { it?.styles?.isNotEmpty() ?: false })
|
||||||
|
contentProperty().bind(styleBoxProperty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
package hep.dataforge.vis.fx.editor
|
||||||
|
|
||||||
|
import hep.dataforge.vis.common.VisualGroup
|
||||||
|
import hep.dataforge.vis.common.VisualObject
|
||||||
|
import javafx.beans.property.SimpleObjectProperty
|
||||||
|
import javafx.scene.control.SelectionMode
|
||||||
|
import javafx.scene.control.TreeItem
|
||||||
|
import tornadofx.*
|
||||||
|
|
||||||
|
private fun toTreeItem(visualObject: VisualObject, title: String): TreeItem<Pair<String, VisualObject>> {
|
||||||
|
return object : TreeItem<Pair<String, VisualObject>>(title to visualObject) {
|
||||||
|
init {
|
||||||
|
if (visualObject is VisualGroup) {
|
||||||
|
//lazy populate the tree
|
||||||
|
expandedProperty().onChange { expanded ->
|
||||||
|
if (expanded && children.isEmpty()) {
|
||||||
|
children.setAll(visualObject.children.map {
|
||||||
|
toTreeItem(it.value, it.key.toString())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isLeaf(): Boolean {
|
||||||
|
return !(visualObject is VisualGroup && visualObject.children.isNotEmpty())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class VisualObjectTreeFragment : Fragment() {
|
||||||
|
val itemProperty = SimpleObjectProperty<VisualObject>()
|
||||||
|
var item: VisualObject? by itemProperty
|
||||||
|
|
||||||
|
val selectedProperty = SimpleObjectProperty<VisualObject>()
|
||||||
|
|
||||||
|
override val root = vbox {
|
||||||
|
titledpane("Object tree", collapsible = false) {
|
||||||
|
treeview<Pair<String, VisualObject>> {
|
||||||
|
cellFormat {
|
||||||
|
text = item.first
|
||||||
|
}
|
||||||
|
itemProperty.onChange { rootObject ->
|
||||||
|
if (rootObject != null) {
|
||||||
|
root = toTreeItem(rootObject, "world")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
selectionModel.selectionMode = SelectionMode.SINGLE
|
||||||
|
val selectedValue = selectionModel.selectedItemProperty().objectBinding { it?.value?.second }
|
||||||
|
selectedProperty.bind(selectedValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
@ -4,9 +4,9 @@ import hep.dataforge.descriptors.NodeDescriptor
|
|||||||
import hep.dataforge.meta.buildMeta
|
import hep.dataforge.meta.buildMeta
|
||||||
import hep.dataforge.meta.toConfig
|
import hep.dataforge.meta.toConfig
|
||||||
import hep.dataforge.values.ValueType
|
import hep.dataforge.values.ValueType
|
||||||
import hep.dataforge.vis.fx.meta.ConfigEditor
|
import hep.dataforge.vis.fx.editor.ConfigEditor
|
||||||
import hep.dataforge.vis.fx.meta.FXMeta
|
import hep.dataforge.vis.fx.editor.FXMeta
|
||||||
import hep.dataforge.vis.fx.meta.MetaViewer
|
import hep.dataforge.vis.fx.editor.MetaViewer
|
||||||
import javafx.geometry.Orientation
|
import javafx.geometry.Orientation
|
||||||
import tornadofx.*
|
import tornadofx.*
|
||||||
|
|
||||||
@ -16,16 +16,16 @@ class MetaEditorDemoApp : App(MetaEditorDemo::class)
|
|||||||
class MetaEditorDemo : View("Meta editor demo") {
|
class MetaEditorDemo : View("Meta editor demo") {
|
||||||
|
|
||||||
val meta = buildMeta {
|
val meta = buildMeta {
|
||||||
"aNode" to {
|
"aNode" put {
|
||||||
"innerNode" to {
|
"innerNode" put {
|
||||||
"innerValue" to true
|
"innerValue" put true
|
||||||
}
|
}
|
||||||
"b" to 223
|
"b" put 223
|
||||||
"c" to "StringValue"
|
"c" put "StringValue"
|
||||||
}
|
}
|
||||||
}.toConfig()
|
}.toConfig()
|
||||||
|
|
||||||
val descriptor = NodeDescriptor.build {
|
val descriptor = NodeDescriptor {
|
||||||
node("aNode") {
|
node("aNode") {
|
||||||
info = "A root demo node"
|
info = "A root demo node"
|
||||||
value("b") {
|
value("b") {
|
@ -1,23 +0,0 @@
|
|||||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
|
||||||
import org.openjfx.gradle.JavaFXOptions
|
|
||||||
|
|
||||||
plugins {
|
|
||||||
kotlin("jvm")
|
|
||||||
id("org.openjfx.javafxplugin")
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
api(project(":dataforge-vis-common"))
|
|
||||||
api("no.tornado:tornadofx:1.7.19")
|
|
||||||
api("no.tornado:tornadofx-controlsfx:0.1")
|
|
||||||
}
|
|
||||||
|
|
||||||
configure<JavaFXOptions> {
|
|
||||||
modules("javafx.controls")
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.withType<KotlinCompile> {
|
|
||||||
kotlinOptions{
|
|
||||||
jvmTarget = "1.8"
|
|
||||||
}
|
|
||||||
}
|
|
@ -74,5 +74,5 @@ class JSRootDemoApp : ApplicationBase() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun dispose() = emptyMap<String, Any>()//mapOf("lines" to presenter.dispose())
|
override fun dispose() = emptyMap<String, Any>()//mapOf("lines" put presenter.dispose())
|
||||||
}
|
}
|
@ -15,37 +15,37 @@ class JSRootGeometry(parent: VisualObject?, meta: Meta) : DisplayLeaf(parent, me
|
|||||||
var facesLimit by int(0)
|
var facesLimit by int(0)
|
||||||
|
|
||||||
fun box(xSize: Number, ySize: Number, zSize: Number) = buildMeta {
|
fun box(xSize: Number, ySize: Number, zSize: Number) = buildMeta {
|
||||||
"_typename" to "TGeoBBox"
|
"_typename" put "TGeoBBox"
|
||||||
"fDX" to xSize
|
"fDX" put xSize
|
||||||
"fDY" to ySize
|
"fDY" put ySize
|
||||||
"fDZ" to zSize
|
"fDZ" put zSize
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a GDML union
|
* Create a GDML union
|
||||||
*/
|
*/
|
||||||
operator fun Meta.plus(other: Meta) = buildMeta {
|
operator fun Meta.plus(other: Meta) = buildMeta {
|
||||||
"fNode.fLeft" to this
|
"fNode.fLeft" put this
|
||||||
"fNode.fRight" to other
|
"fNode.fRight" put other
|
||||||
"fNode._typename" to "TGeoUnion"
|
"fNode._typename" put "TGeoUnion"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a GDML subtraction
|
* Create a GDML subtraction
|
||||||
*/
|
*/
|
||||||
operator fun Meta.minus(other: Meta) = buildMeta {
|
operator fun Meta.minus(other: Meta) = buildMeta {
|
||||||
"fNode.fLeft" to this
|
"fNode.fLeft" put this
|
||||||
"fNode.fRight" to other
|
"fNode.fRight" put other
|
||||||
"fNode._typename" to "TGeoSubtraction"
|
"fNode._typename" put "TGeoSubtraction"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Intersect two GDML geometries
|
* Intersect two GDML geometries
|
||||||
*/
|
*/
|
||||||
infix fun Meta.intersect(other: Meta) = buildMeta {
|
infix fun Meta.intersect(other: Meta) = buildMeta {
|
||||||
"fNode.fLeft" to this
|
"fNode.fLeft" put this
|
||||||
"fNode.fRight" to other
|
"fNode.fRight" put other
|
||||||
"fNode._typename" to "TGeoIntersection"
|
"fNode._typename" put "TGeoIntersection"
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -7,13 +7,14 @@ 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-dev-1")
|
api("scientifik:gdml:0.1.4")
|
||||||
}
|
|
||||||
}
|
|
||||||
val jsMain by getting {
|
|
||||||
dependencies {
|
|
||||||
api(project(":dataforge-vis-spatial"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//tasks{
|
||||||
|
// val jsBrowserWebpack by getting(KotlinWebpack::class) {
|
||||||
|
// sourceMaps = false
|
||||||
|
// }
|
||||||
|
//}
|
@ -1,20 +1,21 @@
|
|||||||
package hep.dataforge.vis.spatial.gdml
|
package hep.dataforge.vis.spatial.gdml
|
||||||
|
|
||||||
import hep.dataforge.meta.Meta
|
import hep.dataforge.meta.Meta
|
||||||
|
import hep.dataforge.meta.MetaBuilder
|
||||||
import hep.dataforge.meta.buildMeta
|
import hep.dataforge.meta.buildMeta
|
||||||
import hep.dataforge.meta.builder
|
import hep.dataforge.names.Name
|
||||||
import hep.dataforge.vis.spatial.VisualGroup3D
|
import hep.dataforge.names.toName
|
||||||
import scientifik.gdml.GDML
|
import hep.dataforge.vis.common.useStyle
|
||||||
import scientifik.gdml.GDMLGroup
|
import hep.dataforge.vis.spatial.*
|
||||||
import scientifik.gdml.GDMLMaterial
|
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_COLOR_KEY
|
||||||
import scientifik.gdml.GDMLSolid
|
import scientifik.gdml.*
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
class GDMLTransformer(val root: GDML) {
|
class GDMLTransformer(val root: GDML) {
|
||||||
private val materialCache = HashMap<GDMLMaterial, Meta>()
|
//private val materialCache = HashMap<GDMLMaterial, Meta>()
|
||||||
private val random = Random(111)
|
private val random = Random(222)
|
||||||
|
|
||||||
enum class Action{
|
enum class Action {
|
||||||
ACCEPT,
|
ACCEPT,
|
||||||
REJECT,
|
REJECT,
|
||||||
CACHE
|
CACHE
|
||||||
@ -23,45 +24,60 @@ class GDMLTransformer(val root: GDML) {
|
|||||||
/**
|
/**
|
||||||
* A special group for local templates
|
* A special group for local templates
|
||||||
*/
|
*/
|
||||||
val templates by lazy { VisualGroup3D() }
|
val proto by lazy { VisualGroup3D() }
|
||||||
|
private val styleCache = HashMap<Name, Meta>()
|
||||||
|
|
||||||
var lUnit: LUnit = LUnit.MM
|
var lUnit: LUnit = LUnit.MM
|
||||||
var resolveColor: ColorResolver = { material, _ ->
|
|
||||||
val materialColor = materialCache.getOrPut(material) {
|
|
||||||
buildMeta {
|
|
||||||
"color" to random.nextInt(0, Int.MAX_VALUE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this?.physVolumes?.isEmpty() != false) {
|
|
||||||
materialColor
|
|
||||||
} else {
|
|
||||||
materialColor.builder().apply { "opacity" to 0.5 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var solidAction: (GDMLSolid) -> Action = { Action.CACHE }
|
var solidAction: (GDMLSolid) -> Action = { Action.CACHE }
|
||||||
var volumeAction: (GDMLGroup) -> Action = { Action.ACCEPT }
|
var volumeAction: (GDMLGroup) -> Action = { Action.CACHE }
|
||||||
|
|
||||||
fun printStatistics() {
|
|
||||||
println("Solids:")
|
var solidConfiguration: VisualObject3D.(parent: GDMLVolume, solid: GDMLSolid) -> Unit = { parent, _ ->
|
||||||
solidCounter.entries.sortedByDescending { it.value }.forEach {
|
lUnit = LUnit.CM
|
||||||
println("\t$it")
|
if (parent.physVolumes.isNotEmpty()) {
|
||||||
|
useStyle("opaque") {
|
||||||
|
Material3D.MATERIAL_OPACITY_KEY put 0.3
|
||||||
|
}
|
||||||
}
|
}
|
||||||
println("Solids total: ${solidCounter.values.sum()}")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val solidCounter = HashMap<String, Int>()
|
fun VisualObject3D.useStyle(name: String, builder: MetaBuilder.() -> Unit) {
|
||||||
|
styleCache.getOrPut(name.toName()) {
|
||||||
internal fun solidAdded(solid: GDMLSolid) {
|
buildMeta(builder)
|
||||||
solidCounter[solid.name] = (solidCounter[solid.name] ?: 0) + 1
|
|
||||||
}
|
}
|
||||||
|
useStyle(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun configureSolid(obj: VisualObject3D, parent: GDMLVolume, solid: GDMLSolid) {
|
||||||
|
val material = parent.materialref.resolve(root) ?: GDMLElement(parent.materialref.ref)
|
||||||
|
|
||||||
|
val styleName = "material[${material.name}]"
|
||||||
|
|
||||||
|
obj.useStyle(styleName) {
|
||||||
|
MATERIAL_COLOR_KEY put random.nextInt(16777216)
|
||||||
|
"gdml.material" put material.name
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.solidConfiguration(parent, solid)
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// internal fun solidAdded(solid: GDMLSolid) {
|
||||||
|
// solidCounter[solid.name] = (solidCounter[solid.name] ?: 0) + 1
|
||||||
|
// }
|
||||||
|
|
||||||
var onFinish: GDMLTransformer.() -> Unit = {}
|
var onFinish: GDMLTransformer.() -> Unit = {}
|
||||||
|
|
||||||
internal fun finished(final: VisualGroup3D) {
|
internal fun finalize(final: VisualGroup3D): VisualGroup3D {
|
||||||
final.templates = templates
|
final.prototypes = proto
|
||||||
|
styleCache.forEach {
|
||||||
|
final.styleSheet {
|
||||||
|
define(it.key.toString(), it.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final.rotationOrder = RotationOrder.ZXY
|
||||||
onFinish(this@GDMLTransformer)
|
onFinish(this@GDMLTransformer)
|
||||||
|
return final
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,9 +1,13 @@
|
|||||||
package hep.dataforge.vis.spatial.gdml
|
package hep.dataforge.vis.spatial.gdml
|
||||||
|
|
||||||
import hep.dataforge.meta.Meta
|
|
||||||
|
import hep.dataforge.names.Name
|
||||||
|
import hep.dataforge.names.asName
|
||||||
import hep.dataforge.names.plus
|
import hep.dataforge.names.plus
|
||||||
import hep.dataforge.vis.common.asName
|
import hep.dataforge.vis.common.get
|
||||||
import hep.dataforge.vis.spatial.*
|
import hep.dataforge.vis.spatial.*
|
||||||
|
import hep.dataforge.vis.spatial.World.ONE
|
||||||
|
import hep.dataforge.vis.spatial.World.ZERO
|
||||||
import scientifik.gdml.*
|
import scientifik.gdml.*
|
||||||
import kotlin.math.cos
|
import kotlin.math.cos
|
||||||
import kotlin.math.sin
|
import kotlin.math.sin
|
||||||
@ -11,41 +15,45 @@ import kotlin.math.sin
|
|||||||
|
|
||||||
private fun VisualObject3D.withPosition(
|
private fun VisualObject3D.withPosition(
|
||||||
lUnit: LUnit,
|
lUnit: LUnit,
|
||||||
pos: GDMLPosition? = null,
|
newPos: GDMLPosition? = null,
|
||||||
rotation: GDMLRotation? = null,
|
newRotation: GDMLRotation? = null,
|
||||||
scale: GDMLScale? = null
|
newScale: GDMLScale? = null
|
||||||
): VisualObject3D = apply {
|
): VisualObject3D = apply {
|
||||||
pos?.let {
|
newPos?.let {
|
||||||
this@withPosition.x = pos.x(lUnit)
|
val point = Point3D(it.x(lUnit), it.y(lUnit), it.z(lUnit))
|
||||||
this@withPosition.y = pos.y(lUnit)
|
if (position != null || point != ZERO) {
|
||||||
this@withPosition.z = pos.z(lUnit)
|
position = point
|
||||||
}
|
}
|
||||||
rotation?.let {
|
|
||||||
this@withPosition.rotationX = rotation.x()
|
|
||||||
this@withPosition.rotationY = rotation.y()
|
|
||||||
this@withPosition.rotationZ = rotation.z()
|
|
||||||
this@withPosition.rotationOrder=RotationOrder.ZXY
|
|
||||||
}
|
}
|
||||||
scale?.let {
|
newRotation?.let {
|
||||||
this@withPosition.scaleX = scale.x.toFloat()
|
val point = Point3D(it.x(), it.y(), it.z())
|
||||||
this@withPosition.scaleY = scale.y.toFloat()
|
if (rotation != null || point != ZERO) {
|
||||||
this@withPosition.scaleZ = scale.z.toFloat()
|
rotation = point
|
||||||
|
}
|
||||||
|
//this@withPosition.rotationOrder = RotationOrder.ZXY
|
||||||
|
}
|
||||||
|
newScale?.let {
|
||||||
|
val point = Point3D(it.x, it.y, it.z)
|
||||||
|
if (scale != null || point != ONE) {
|
||||||
|
scale = point
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//TODO convert units if needed
|
//TODO convert units if needed
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
private inline operator fun Number.times(d: Double) = toDouble() * d
|
private inline operator fun Number.times(d: Double) = toDouble() * d
|
||||||
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
private inline operator fun Number.times(f: Float) = toFloat() * f
|
private inline operator fun Number.times(f: Float) = toFloat() * f
|
||||||
|
|
||||||
private fun VisualGroup3D.addSolid(
|
private fun VisualGroup3D.addSolid(
|
||||||
context: GDMLTransformer,
|
context: GDMLTransformer,
|
||||||
solid: GDMLSolid,
|
solid: GDMLSolid,
|
||||||
name: String? = null,
|
name: String = "",
|
||||||
block: VisualObject3D.() -> Unit = {}
|
block: VisualObject3D.() -> Unit = {}
|
||||||
): VisualObject3D {
|
): VisualObject3D {
|
||||||
context.solidAdded(solid)
|
//context.solidAdded(solid)
|
||||||
val lScale = solid.lscale(context.lUnit)
|
val lScale = solid.lscale(context.lUnit)
|
||||||
val aScale = solid.ascale()
|
val aScale = solid.ascale()
|
||||||
return when (solid) {
|
return when (solid) {
|
||||||
@ -78,7 +86,7 @@ private fun VisualGroup3D.addSolid(
|
|||||||
val innerSolid = solid.solidref.resolve(context.root)
|
val innerSolid = solid.solidref.resolve(context.root)
|
||||||
?: error("Solid with tag ${solid.solidref.ref} for scaled solid ${solid.name} not defined")
|
?: error("Solid with tag ${solid.solidref.ref} for scaled solid ${solid.name} not defined")
|
||||||
|
|
||||||
addSolid(context, innerSolid) {
|
addSolid(context, innerSolid, name) {
|
||||||
block()
|
block()
|
||||||
scaleX *= solid.scale.x.toFloat()
|
scaleX *= solid.scale.x.toFloat()
|
||||||
scaleY *= solid.scale.y.toFloat()
|
scaleY *= solid.scale.y.toFloat()
|
||||||
@ -147,7 +155,8 @@ private fun VisualGroup3D.addPhysicalVolume(
|
|||||||
|
|
||||||
when (context.volumeAction(volume)) {
|
when (context.volumeAction(volume)) {
|
||||||
GDMLTransformer.Action.ACCEPT -> {
|
GDMLTransformer.Action.ACCEPT -> {
|
||||||
this[physVolume.name] = volume(context, volume).apply {
|
val group = volume(context, volume)
|
||||||
|
this[physVolume.name ?: ""] = group.apply {
|
||||||
withPosition(
|
withPosition(
|
||||||
context.lUnit,
|
context.lUnit,
|
||||||
physVolume.resolvePosition(context.root),
|
physVolume.resolvePosition(context.root),
|
||||||
@ -157,12 +166,12 @@ private fun VisualGroup3D.addPhysicalVolume(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
GDMLTransformer.Action.CACHE -> {
|
GDMLTransformer.Action.CACHE -> {
|
||||||
val name = volumesName + volume.name.asName()
|
val fullName = volumesName + volume.name.asName()
|
||||||
if (context.templates[name] == null) {
|
if (context.proto[fullName] == null) {
|
||||||
context.templates[name] = volume(context, volume)
|
context.proto[fullName] = volume(context, volume)
|
||||||
}
|
}
|
||||||
|
|
||||||
this[physVolume.name] = Proxy(name).apply {
|
this[physVolume.name ?: ""] = Proxy(fullName).apply {
|
||||||
withPosition(
|
withPosition(
|
||||||
context.lUnit,
|
context.lUnit,
|
||||||
physVolume.resolvePosition(context.root),
|
physVolume.resolvePosition(context.root),
|
||||||
@ -185,7 +194,8 @@ private fun VisualGroup3D.addDivisionVolume(
|
|||||||
?: error("Volume with ref ${divisionVolume.volumeref.ref} could not be resolved")
|
?: error("Volume with ref ${divisionVolume.volumeref.ref} could not be resolved")
|
||||||
|
|
||||||
//TODO add divisions
|
//TODO add divisions
|
||||||
add(
|
set(
|
||||||
|
Name.EMPTY,
|
||||||
volume(
|
volume(
|
||||||
context,
|
context,
|
||||||
volume
|
volume
|
||||||
@ -193,12 +203,7 @@ private fun VisualGroup3D.addDivisionVolume(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun VisualGroup3D.addVolume(
|
private val solidsName = "solids".asName()
|
||||||
context: GDMLTransformer,
|
|
||||||
group: GDMLGroup
|
|
||||||
) {
|
|
||||||
this[group.name] = volume(context, group)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun volume(
|
private fun volume(
|
||||||
context: GDMLTransformer,
|
context: GDMLTransformer,
|
||||||
@ -208,18 +213,17 @@ private fun volume(
|
|||||||
if (group is GDMLVolume) {
|
if (group is GDMLVolume) {
|
||||||
val solid = group.solidref.resolve(context.root)
|
val solid = group.solidref.resolve(context.root)
|
||||||
?: error("Solid with tag ${group.solidref.ref} for volume ${group.name} not defined")
|
?: error("Solid with tag ${group.solidref.ref} for volume ${group.name} not defined")
|
||||||
val material = group.materialref.resolve(context.root) ?: GDMLElement(group.materialref.ref)
|
|
||||||
|
|
||||||
when (context.solidAction(solid)) {
|
when (context.solidAction(solid)) {
|
||||||
GDMLTransformer.Action.ACCEPT -> {
|
GDMLTransformer.Action.ACCEPT -> {
|
||||||
addSolid(context, solid, solid.name) {
|
addSolid(context, solid, solid.name) {
|
||||||
this.material = context.resolveColor(group, material, solid)
|
context.configureSolid(this, group, solid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GDMLTransformer.Action.CACHE -> {
|
GDMLTransformer.Action.CACHE -> {
|
||||||
if (context.templates[solid.name] == null) {
|
if (context.proto[solid.name] == null) {
|
||||||
context.templates.addSolid(context, solid, solid.name) {
|
context.proto.addSolid(context, solid, solid.name) {
|
||||||
this.material = context.resolveColor(group, material, solid)
|
context.configureSolid(this, group, solid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ref(solid.name.asName(), solid.name)
|
ref(solid.name.asName(), solid.name)
|
||||||
@ -241,14 +245,18 @@ private fun volume(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
typealias ColorResolver = GDMLGroup?.(GDMLMaterial, GDMLSolid?) -> Meta
|
|
||||||
|
|
||||||
|
|
||||||
fun GDML.toVisual(block: GDMLTransformer.() -> Unit = {}): VisualGroup3D {
|
fun GDML.toVisual(block: GDMLTransformer.() -> Unit = {}): VisualGroup3D {
|
||||||
|
|
||||||
val context = GDMLTransformer(this).apply(block)
|
val context = GDMLTransformer(this).apply(block)
|
||||||
|
|
||||||
return volume(context, world).also {
|
return context.finalize(volume(context, world))
|
||||||
context.finished(it)
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
|
* Append gdml node to the group
|
||||||
|
*/
|
||||||
|
fun VisualGroup3D.gdml(gdml: GDML, key: String = "", transformer: GDMLTransformer.() -> Unit = {}) {
|
||||||
|
val visual = gdml.toVisual(transformer)
|
||||||
|
//println(Visual3DPlugin.json.stringify(VisualGroup3D.serializer(), visual))
|
||||||
|
set(key, visual)
|
||||||
}
|
}
|
@ -1,128 +0,0 @@
|
|||||||
package hep.dataforge.vis.spatial.gdml.demo
|
|
||||||
|
|
||||||
import hep.dataforge.context.Global
|
|
||||||
import hep.dataforge.vis.hmr.ApplicationBase
|
|
||||||
import hep.dataforge.vis.hmr.startApplication
|
|
||||||
import hep.dataforge.vis.spatial.gdml.GDMLTransformer
|
|
||||||
import hep.dataforge.vis.spatial.gdml.LUnit
|
|
||||||
import hep.dataforge.vis.spatial.gdml.toVisual
|
|
||||||
import hep.dataforge.vis.spatial.three.ThreePlugin
|
|
||||||
import hep.dataforge.vis.spatial.three.output
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.w3c.dom.HTMLDivElement
|
|
||||||
import org.w3c.dom.events.Event
|
|
||||||
import org.w3c.files.FileList
|
|
||||||
import org.w3c.files.FileReader
|
|
||||||
import org.w3c.files.get
|
|
||||||
import scientifik.gdml.GDML
|
|
||||||
import kotlin.browser.document
|
|
||||||
import kotlin.dom.clear
|
|
||||||
|
|
||||||
private class GDMLDemoApp : ApplicationBase() {
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle mouse drag according to https://www.html5rocks.com/en/tutorials/file/dndfiles/
|
|
||||||
*/
|
|
||||||
private fun handleDragOver(event: Event) {
|
|
||||||
event.stopPropagation()
|
|
||||||
event.preventDefault()
|
|
||||||
event.asDynamic().dataTransfer.dropEffect = "copy"
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load data from text file
|
|
||||||
*/
|
|
||||||
private fun loadData(event: Event, block: suspend CoroutineScope.(String) -> Unit) {
|
|
||||||
event.stopPropagation()
|
|
||||||
event.preventDefault()
|
|
||||||
|
|
||||||
|
|
||||||
val file = (event.asDynamic().dataTransfer.files as FileList)[0]
|
|
||||||
?: throw RuntimeException("Failed to load file")
|
|
||||||
|
|
||||||
FileReader().apply {
|
|
||||||
onload = {
|
|
||||||
val string = result as String
|
|
||||||
GlobalScope.launch {
|
|
||||||
block(string)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
readAsText(file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun spinner(show: Boolean) {
|
|
||||||
val style = if (show) {
|
|
||||||
"display:block;"
|
|
||||||
} else {
|
|
||||||
"display:none;"
|
|
||||||
}
|
|
||||||
document.getElementById("loader")?.setAttribute("style", style)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun message(message: String?) {
|
|
||||||
val element = document.getElementById("message")
|
|
||||||
if (message == null) {
|
|
||||||
element?.setAttribute("style", "display:none;")
|
|
||||||
} else {
|
|
||||||
element?.textContent = message
|
|
||||||
element?.setAttribute("style", "display:block;")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
override fun start(state: Map<String, Any>) {
|
|
||||||
|
|
||||||
val context = Global.context("demo") {}
|
|
||||||
val three = context.plugins.load(ThreePlugin)
|
|
||||||
//val url = URL("https://drive.google.com/open?id=1w5e7fILMN83JGgB8WANJUYm8OW2s0WVO")
|
|
||||||
|
|
||||||
val canvas = document.getElementById("canvas") ?: error("Element with id canvas not found on page")
|
|
||||||
canvas.clear()
|
|
||||||
|
|
||||||
val action: suspend CoroutineScope.(String) -> Unit = { it ->
|
|
||||||
canvas.clear()
|
|
||||||
launch { spinner(true) }
|
|
||||||
launch { message("Loading GDML") }
|
|
||||||
val gdml = GDML.format.parse(GDML.serializer(), it)
|
|
||||||
launch { message("Converting GDML into DF-VIS format") }
|
|
||||||
val visual = gdml.toVisual {
|
|
||||||
lUnit = LUnit.CM
|
|
||||||
volumeAction = { volume ->
|
|
||||||
when {
|
|
||||||
volume.name.startsWith("ecal") -> GDMLTransformer.Action.REJECT
|
|
||||||
volume.name.startsWith("U") -> GDMLTransformer.Action.CACHE
|
|
||||||
volume.name.startsWith("V") -> GDMLTransformer.Action.CACHE
|
|
||||||
else -> GDMLTransformer.Action.ACCEPT
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
launch { message("Rendering") }
|
|
||||||
val output = three.output(canvas){
|
|
||||||
"axis" to {
|
|
||||||
"size" to 100
|
|
||||||
}
|
|
||||||
}
|
|
||||||
output.render(visual)
|
|
||||||
launch {
|
|
||||||
message(null)
|
|
||||||
spinner(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(document.getElementById("drop_zone") as? HTMLDivElement)?.apply {
|
|
||||||
addEventListener("dragover", { handleDragOver(it) }, false)
|
|
||||||
addEventListener("drop", { loadData(it, action) }, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fun main() {
|
|
||||||
startApplication(::GDMLDemoApp)
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<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="main.css">
|
|
||||||
<script type="text/javascript" src="main.bundle.js"></script>
|
|
||||||
</head>
|
|
||||||
<body class="testApp">
|
|
||||||
<div class="container" id="drop_zone" data-toggle="tooltip" data-placement="right"
|
|
||||||
title="Для загрузки данных в текстовом формате, надо перетащить файл сюда">
|
|
||||||
Загрузить данные
|
|
||||||
<br/>
|
|
||||||
(перетащить файл сюда)
|
|
||||||
</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="message" style="display:none;"></div>
|
|
||||||
<div class="container" id="canvas"></div>
|
|
||||||
|
|
||||||
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
|
|
||||||
integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
|
|
||||||
crossorigin="anonymous"></script>
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"
|
|
||||||
integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1"
|
|
||||||
crossorigin="anonymous"></script>
|
|
||||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
|
|
||||||
integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"
|
|
||||||
crossorigin="anonymous"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,13 +0,0 @@
|
|||||||
.loader {
|
|
||||||
border: 16px solid #f3f3f3; /* Light grey */
|
|
||||||
border-top: 16px solid #3498db; /* Blue */
|
|
||||||
border-radius: 50%;
|
|
||||||
width: 120px;
|
|
||||||
height: 120px;
|
|
||||||
animation: spin 2s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
0% { transform: rotate(0deg); }
|
|
||||||
100% { transform: rotate(360deg); }
|
|
||||||
}
|
|
@ -0,0 +1,169 @@
|
|||||||
|
package hep.dataforge.vis.spatial.gdml
|
||||||
|
|
||||||
|
import hep.dataforge.meta.Meta
|
||||||
|
import hep.dataforge.vis.spatial.*
|
||||||
|
import kotlinx.serialization.*
|
||||||
|
import kotlinx.serialization.json.*
|
||||||
|
import kotlinx.serialization.modules.SerialModule
|
||||||
|
import kotlinx.serialization.modules.SerialModuleCollector
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
internal val SerialDescriptor.jsonType
|
||||||
|
get() = when (this.kind) {
|
||||||
|
StructureKind.LIST -> "array"
|
||||||
|
PrimitiveKind.BYTE, PrimitiveKind.SHORT, PrimitiveKind.INT, PrimitiveKind.LONG,
|
||||||
|
PrimitiveKind.FLOAT, PrimitiveKind.DOUBLE -> "number"
|
||||||
|
PrimitiveKind.STRING, PrimitiveKind.CHAR, UnionKind.ENUM_KIND -> "string"
|
||||||
|
PrimitiveKind.BOOLEAN -> "boolean"
|
||||||
|
else -> "object"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun SerialModule.enumerate(type: KClass<*>): Sequence<SerialDescriptor> {
|
||||||
|
val list = ArrayList<SerialDescriptor>()
|
||||||
|
fun send(descriptor: SerialDescriptor) = list.add(descriptor)
|
||||||
|
|
||||||
|
val enumerator = object : SerialModuleCollector {
|
||||||
|
override fun <T : Any> contextual(kClass: KClass<T>, serializer: KSerializer<T>) {
|
||||||
|
if (kClass == type) {
|
||||||
|
send(serializer.descriptor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun <Base : Any, Sub : Base> polymorphic(
|
||||||
|
baseClass: KClass<Base>,
|
||||||
|
actualClass: KClass<Sub>,
|
||||||
|
actualSerializer: KSerializer<Sub>
|
||||||
|
) {
|
||||||
|
if (baseClass == type) {
|
||||||
|
send(actualSerializer.descriptor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
dumpTo(enumerator)
|
||||||
|
return list.asSequence()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an [JsonObject] which contains Json Schema of given [descriptor].
|
||||||
|
*
|
||||||
|
* Schema can contain following fields:
|
||||||
|
* `description`, `type` for all descriptors;
|
||||||
|
* `properties` and `required` for objects;
|
||||||
|
* `enum` for enums;
|
||||||
|
* `items` for arrays.
|
||||||
|
*
|
||||||
|
* User can modify this schema to add additional validation keywords
|
||||||
|
* (as per [https://json-schema.org/latest/json-schema-validation.html])
|
||||||
|
* if they want.
|
||||||
|
*/
|
||||||
|
private fun jsonSchema(descriptor: SerialDescriptor, context: SerialModule): JsonObject {
|
||||||
|
|
||||||
|
if (descriptor.name in arrayOf(
|
||||||
|
"hep.dataforge.vis.spatial.Point3D",
|
||||||
|
"hep.dataforge.vis.spatial.Point2D",
|
||||||
|
Meta::class.qualifiedName
|
||||||
|
)
|
||||||
|
) return json {
|
||||||
|
"\$ref" to "#/definitions/${descriptor.name}"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val properties: MutableMap<String, JsonObject> = mutableMapOf()
|
||||||
|
val requiredProperties: MutableSet<String> = mutableSetOf()
|
||||||
|
val isEnum = descriptor.kind == UnionKind.ENUM_KIND
|
||||||
|
val isPolymorphic = descriptor.kind is PolymorphicKind
|
||||||
|
|
||||||
|
|
||||||
|
if (!isEnum && !isPolymorphic) descriptor.elementDescriptors().forEachIndexed { index, child ->
|
||||||
|
val elementName = descriptor.getElementName(index)
|
||||||
|
|
||||||
|
properties[elementName] = when (elementName) {
|
||||||
|
"templates" -> json {
|
||||||
|
"\$ref" to "#/definitions/hep.dataforge.vis.spatial.VisualGroup3D"
|
||||||
|
}
|
||||||
|
"properties" -> json {
|
||||||
|
"\$ref" to "#/definitions/${Meta::class.qualifiedName}"
|
||||||
|
}
|
||||||
|
"first", "second" -> json{
|
||||||
|
"\$ref" to "#/definitions/children"
|
||||||
|
}
|
||||||
|
"styleSheet" -> json {
|
||||||
|
"type" to "object"
|
||||||
|
"additionalProperties" to json {
|
||||||
|
"\$ref" to "#/definitions/${Meta::class.qualifiedName}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
in arrayOf("children") -> json {
|
||||||
|
"type" to "object"
|
||||||
|
"additionalProperties" to json {
|
||||||
|
"\$ref" to "#/definitions/children"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> jsonSchema(child, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!descriptor.isElementOptional(index)) requiredProperties.add(elementName)
|
||||||
|
}
|
||||||
|
|
||||||
|
val jsonType = descriptor.jsonType
|
||||||
|
val objectData: MutableMap<String, JsonElement> = mutableMapOf(
|
||||||
|
"description" to JsonLiteral(descriptor.name),
|
||||||
|
"type" to JsonLiteral(jsonType)
|
||||||
|
)
|
||||||
|
if (isEnum) {
|
||||||
|
val allElementNames = (0 until descriptor.elementsCount).map(descriptor::getElementName)
|
||||||
|
objectData += "enum" to JsonArray(allElementNames.map(::JsonLiteral))
|
||||||
|
}
|
||||||
|
when (jsonType) {
|
||||||
|
"object" -> {
|
||||||
|
objectData["properties"] = JsonObject(properties)
|
||||||
|
val required = requiredProperties.map { JsonLiteral(it) }
|
||||||
|
if (required.isNotEmpty()) {
|
||||||
|
objectData["required"] = JsonArray(required)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"array" -> objectData["items"] = properties.values.let {
|
||||||
|
check(it.size == 1) { "Array descriptor has returned inconsistent number of elements: expected 1, found ${it.size}" }
|
||||||
|
it.first()
|
||||||
|
}
|
||||||
|
else -> { /* no-op */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return JsonObject(objectData)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
val context = Visual3DPlugin.serialModule
|
||||||
|
val definitions = json {
|
||||||
|
"children" to json {
|
||||||
|
"anyOf" to jsonArray {
|
||||||
|
context.enumerate(VisualObject3D::class).forEach {
|
||||||
|
if (it.name == "hep.dataforge.vis.spatial.VisualGroup3D") {
|
||||||
|
+json {
|
||||||
|
"\$ref" to "#/definitions/${it.name}"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
+jsonSchema(it, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"hep.dataforge.vis.spatial.Point3D" to jsonSchema(Point3DSerializer.descriptor, context)
|
||||||
|
"hep.dataforge.vis.spatial.Point2D" to jsonSchema(Point2DSerializer.descriptor, context)
|
||||||
|
"hep.dataforge.vis.spatial.VisualGroup3D" to jsonSchema(VisualGroup3D.serializer().descriptor, context)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
println(
|
||||||
|
Json.indented.stringify(
|
||||||
|
JsonObjectSerializer,
|
||||||
|
json {
|
||||||
|
"definitions" to definitions
|
||||||
|
"\$ref" to "#/definitions/hep.dataforge.vis.spatial.VisualGroup3D"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -1,26 +0,0 @@
|
|||||||
package hep.dataforge.vis.spatial.gdml
|
|
||||||
|
|
||||||
import hep.dataforge.vis.spatial.Visual3DPlugin
|
|
||||||
import hep.dataforge.vis.spatial.VisualGroup3D
|
|
||||||
import nl.adaptivity.xmlutil.StAXReader
|
|
||||||
import scientifik.gdml.GDML
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
|
|
||||||
fun main() {
|
|
||||||
val file = File("D:\\Work\\Projects\\gdml.kt\\gdml-source\\BM@N_coil.gdml")
|
|
||||||
|
|
||||||
val xmlReader = StAXReader(file.inputStream(), "UTF-8")
|
|
||||||
val xml = GDML.format.parse(GDML.serializer(), xmlReader)
|
|
||||||
val visual = xml.toVisual {
|
|
||||||
lUnit = LUnit.CM
|
|
||||||
}
|
|
||||||
|
|
||||||
//val meta = visual.toMeta()
|
|
||||||
|
|
||||||
val str = Visual3DPlugin.json.stringify(VisualGroup3D.serializer(), visual)
|
|
||||||
|
|
||||||
println(str)
|
|
||||||
|
|
||||||
//println(Json.indented.stringify(meta.toJson()))
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
package hep.dataforge.vis.spatial.gdml
|
|
||||||
|
|
||||||
import hep.dataforge.names.toName
|
|
||||||
import hep.dataforge.vis.spatial.VisualGroup3D
|
|
||||||
import nl.adaptivity.xmlutil.StAXReader
|
|
||||||
import scientifik.gdml.GDML
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
fun main() {
|
|
||||||
val file = File("D:\\Work\\Projects\\gdml.kt\\gdml-source\\BM@N.gdml")
|
|
||||||
//val file = File("D:\\Work\\Projects\\gdml.kt\\gdml-source\\cubes.gdml")
|
|
||||||
|
|
||||||
val xmlReader = StAXReader(file.inputStream(), "UTF-8")
|
|
||||||
val xml = GDML.format.parse(GDML.serializer(), xmlReader)
|
|
||||||
val visual = xml.toVisual {
|
|
||||||
lUnit = LUnit.CM
|
|
||||||
volumeAction = { volume ->
|
|
||||||
when {
|
|
||||||
volume.name.startsWith("ecal") -> GDMLTransformer.Action.CACHE
|
|
||||||
volume.name.startsWith("U") -> GDMLTransformer.Action.CACHE
|
|
||||||
volume.name.startsWith("V") -> GDMLTransformer.Action.CACHE
|
|
||||||
else -> GDMLTransformer.Action.ACCEPT
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onFinish = { printStatistics() }
|
|
||||||
}
|
|
||||||
|
|
||||||
val template = visual.getTemplate("volumes.ecal01mod".toName())
|
|
||||||
println(template)
|
|
||||||
visual.flatMap { (it as? VisualGroup3D) ?: listOf(it) }.forEach {
|
|
||||||
if(it.parent==null) error("")
|
|
||||||
}
|
|
||||||
//readLine()
|
|
||||||
//val meta = visual.toMeta()
|
|
||||||
// val tmpFile = File.createTempFile("dataforge-visual", "json")
|
|
||||||
//tmpFile.writeText(meta.toString())
|
|
||||||
//println(tmpFile.absoluteFile)
|
|
||||||
}
|
|
@ -0,0 +1,18 @@
|
|||||||
|
package hep.dataforge.vis.spatial.gdml
|
||||||
|
|
||||||
|
import hep.dataforge.vis.spatial.VisualGroup3D
|
||||||
|
import nl.adaptivity.xmlutil.StAXReader
|
||||||
|
import scientifik.gdml.GDML
|
||||||
|
import java.nio.file.Files
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun VisualGroup3D.gdml(file: Path, key: String = "", transformer: GDMLTransformer.() -> Unit = {}) {
|
||||||
|
val gdml = GDML.readFile(file)
|
||||||
|
gdml(gdml, key, transformer)
|
||||||
|
}
|
||||||
|
|
@ -5,10 +5,12 @@ import org.junit.Test
|
|||||||
import scientifik.gdml.GDML
|
import scientifik.gdml.GDML
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
import kotlin.test.Ignore
|
||||||
|
|
||||||
class TestConvertor {
|
class TestConvertor {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@Ignore
|
||||||
fun testBMNGeometry() {
|
fun testBMNGeometry() {
|
||||||
val url = URL("https://drive.google.com/open?id=1w5e7fILMN83JGgB8WANJUYm8OW2s0WVO")
|
val url = URL("https://drive.google.com/open?id=1w5e7fILMN83JGgB8WANJUYm8OW2s0WVO")
|
||||||
val file = File("D:\\Work\\Projects\\gdml.kt\\gdml-source\\BM@N.gdml")
|
val file = File("D:\\Work\\Projects\\gdml.kt\\gdml-source\\BM@N.gdml")
|
||||||
@ -24,6 +26,7 @@ class TestConvertor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@Ignore
|
||||||
fun testCubes() {
|
fun testCubes() {
|
||||||
val file = File("D:\\Work\\Projects\\gdml.kt\\gdml-source\\cubes.gdml ")
|
val file = File("D:\\Work\\Projects\\gdml.kt\\gdml-source\\cubes.gdml ")
|
||||||
val stream = if (file.exists()) {
|
val stream = if (file.exists()) {
|
||||||
|
@ -1,16 +1,15 @@
|
|||||||
import org.openjfx.gradle.JavaFXOptions
|
import org.openjfx.gradle.JavaFXOptions
|
||||||
|
import scientifik.useSerialization
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("scientifik.mpp")
|
id("scientifik.mpp")
|
||||||
id("org.openjfx.javafxplugin")
|
id("org.openjfx.javafxplugin")
|
||||||
}
|
}
|
||||||
|
|
||||||
scientifik{
|
useSerialization()
|
||||||
serialization = true
|
|
||||||
}
|
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
jvm{
|
jvm {
|
||||||
withJava()
|
withJava()
|
||||||
}
|
}
|
||||||
sourceSets {
|
sourceSets {
|
||||||
@ -19,19 +18,22 @@ kotlin {
|
|||||||
api(project(":dataforge-vis-common"))
|
api(project(":dataforge-vis-common"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jvmMain{
|
jvmMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":dataforge-vis-fx"))
|
implementation("org.fxyz3d:fxyz3d:0.5.2") {
|
||||||
implementation("org.fxyz3d:fxyz3d:0.4.0")
|
exclude(module = "slf4j-simple")
|
||||||
|
}
|
||||||
|
api("org.jetbrains.kotlinx:kotlinx-coroutines-javafx:${Scientifik.coroutinesVersion}")
|
||||||
|
implementation("eu.mihosoft.vrl.jcsg:jcsg:0.5.7") {
|
||||||
|
exclude(module = "slf4j-simple")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jsMain{
|
}
|
||||||
|
jsMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
api("info.laht.threekt:threejs-wrapper:0.106-npm-3")
|
// api(project(":wrappers"))
|
||||||
implementation(npm("three", "0.106.2"))
|
implementation(npm("three", "0.106.2"))
|
||||||
implementation(npm("@hi-level/three-csg", "1.0.6"))
|
implementation(npm("@hi-level/three-csg", "1.0.6"))
|
||||||
implementation(npm("style-loader"))
|
|
||||||
implementation(npm("element-resize-event"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,17 +2,22 @@
|
|||||||
package hep.dataforge.vis.spatial
|
package hep.dataforge.vis.spatial
|
||||||
|
|
||||||
import hep.dataforge.context.Context
|
import hep.dataforge.context.Context
|
||||||
import hep.dataforge.io.ConfigSerializer
|
import hep.dataforge.io.serialization.ConfigSerializer
|
||||||
import hep.dataforge.meta.*
|
import hep.dataforge.meta.Config
|
||||||
|
import hep.dataforge.meta.Meta
|
||||||
|
import hep.dataforge.meta.float
|
||||||
|
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 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
|
||||||
data class Box(
|
@SerialName("3d.box")
|
||||||
|
class Box(
|
||||||
val xSize: Float,
|
val xSize: Float,
|
||||||
val ySize: Float,
|
val ySize: Float,
|
||||||
val zSize: Float
|
val zSize: Float
|
||||||
@ -27,9 +32,9 @@ data class Box(
|
|||||||
|
|
||||||
//TODO add helper for color configuration
|
//TODO add helper for color configuration
|
||||||
override fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>) {
|
override fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>) {
|
||||||
val dx = xSize.toFloat() / 2
|
val dx = xSize / 2
|
||||||
val dy = ySize.toFloat() / 2
|
val dy = ySize / 2
|
||||||
val dz = zSize.toFloat() / 2
|
val dz = zSize / 2
|
||||||
val node1 = Point3D(-dx, -dy, -dz)
|
val node1 = Point3D(-dx, -dy, -dz)
|
||||||
val node2 = Point3D(dx, -dy, -dz)
|
val node2 = Point3D(dx, -dy, -dz)
|
||||||
val node3 = Point3D(dx, dy, -dz)
|
val node3 = Point3D(dx, dy, -dz)
|
||||||
@ -46,17 +51,6 @@ data class Box(
|
|||||||
geometryBuilder.face4(node8, node5, node6, node7)
|
geometryBuilder.face4(node8, node5, node6, node7)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun MetaBuilder.updateMeta() {
|
|
||||||
"xSize" to xSize
|
|
||||||
"ySize" to ySize
|
|
||||||
"zSize" to ySize
|
|
||||||
updatePosition()
|
|
||||||
}
|
|
||||||
|
|
||||||
// override fun toMeta(): Meta {
|
|
||||||
// return (Visual3DPlugin.json.toJson(Box.serializer(), this) as JsonObject).toMeta()
|
|
||||||
// }
|
|
||||||
|
|
||||||
companion object : VisualFactory<Box> {
|
companion object : VisualFactory<Box> {
|
||||||
const val TYPE = "geometry.3d.box"
|
const val TYPE = "geometry.3d.box"
|
||||||
|
|
||||||
@ -76,6 +70,6 @@ inline fun VisualGroup3D.box(
|
|||||||
xSize: Number,
|
xSize: Number,
|
||||||
ySize: Number,
|
ySize: Number,
|
||||||
zSize: Number,
|
zSize: Number,
|
||||||
name: String? = null,
|
name: String = "",
|
||||||
action: Box.() -> Unit = {}
|
action: Box.() -> Unit = {}
|
||||||
) = Box(xSize.toFloat(), ySize.toFloat(), zSize.toFloat()).apply(action).also { set(name, it) }
|
) = Box(xSize.toFloat(), ySize.toFloat(), zSize.toFloat()).apply(action).also { set(name, it) }
|
@ -1,9 +1,9 @@
|
|||||||
@file:UseSerializers(Point3DSerializer::class)
|
@file:UseSerializers(Point3DSerializer::class)
|
||||||
|
|
||||||
package hep.dataforge.vis.spatial
|
package hep.dataforge.vis.spatial
|
||||||
|
|
||||||
import hep.dataforge.io.ConfigSerializer
|
import hep.dataforge.io.serialization.ConfigSerializer
|
||||||
import hep.dataforge.meta.Config
|
import hep.dataforge.meta.Config
|
||||||
import hep.dataforge.meta.MetaBuilder
|
|
||||||
import hep.dataforge.meta.update
|
import hep.dataforge.meta.update
|
||||||
import hep.dataforge.vis.common.AbstractVisualObject
|
import hep.dataforge.vis.common.AbstractVisualObject
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
@ -22,46 +22,49 @@ class Composite(
|
|||||||
val second: VisualObject3D
|
val second: VisualObject3D
|
||||||
) : AbstractVisualObject(), VisualObject3D {
|
) : AbstractVisualObject(), VisualObject3D {
|
||||||
|
|
||||||
|
init {
|
||||||
|
first.parent = this
|
||||||
|
second.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
override var position: Point3D? = null
|
override var position: Point3D? = null
|
||||||
override var rotation: Point3D? = null
|
override var rotation: Point3D? = null
|
||||||
override var scale: Point3D? = null
|
override var scale: Point3D? = null
|
||||||
|
|
||||||
@Serializable(ConfigSerializer::class)
|
@Serializable(ConfigSerializer::class)
|
||||||
override var properties: Config? = null
|
override var properties: Config? = null
|
||||||
|
|
||||||
override fun MetaBuilder.updateMeta() {
|
|
||||||
"compositeType" to compositeType
|
|
||||||
"first" to first.toMeta()
|
|
||||||
"second" to second.toMeta()
|
|
||||||
updatePosition()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun VisualGroup3D.composite(
|
inline fun VisualGroup3D.composite(
|
||||||
type: CompositeType,
|
type: CompositeType,
|
||||||
name: String? = null,
|
name: String = "",
|
||||||
builder: VisualGroup3D.() -> Unit
|
builder: VisualGroup3D.() -> Unit
|
||||||
): Composite {
|
): Composite {
|
||||||
val group = VisualGroup3D().apply(builder)
|
val group = VisualGroup3D().apply(builder)
|
||||||
val children = group.filterIsInstance<VisualObject3D>()
|
val children = group.filterIsInstance<VisualObject3D>()
|
||||||
if (children.size != 2) error("Composite requires exactly two children")
|
if (children.size != 2) error("Composite requires exactly two children")
|
||||||
return Composite(type, children[0], children[1]).also {
|
return Composite(type, children[0], children[1]).also {
|
||||||
if (group.properties != null) {
|
|
||||||
it.config.update(group.config)
|
it.config.update(group.config)
|
||||||
it.material = group.material
|
//it.material = group.material
|
||||||
}
|
|
||||||
|
if(group.position!=null) {
|
||||||
it.position = group.position
|
it.position = group.position
|
||||||
|
}
|
||||||
|
if(group.rotation!=null) {
|
||||||
it.rotation = group.rotation
|
it.rotation = group.rotation
|
||||||
|
}
|
||||||
|
if(group.scale!=null) {
|
||||||
it.scale = group.scale
|
it.scale = group.scale
|
||||||
|
}
|
||||||
set(name, it)
|
set(name, it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun VisualGroup3D.union(name: String? = null, builder: VisualGroup3D.() -> Unit) =
|
fun VisualGroup3D.union(name: String = "", builder: VisualGroup3D.() -> Unit) =
|
||||||
composite(CompositeType.UNION, name, builder = builder)
|
composite(CompositeType.UNION, name, builder = builder)
|
||||||
|
|
||||||
fun VisualGroup3D.subtract(name: String? = null, builder: VisualGroup3D.() -> Unit) =
|
fun VisualGroup3D.subtract(name: String = "", builder: VisualGroup3D.() -> Unit) =
|
||||||
composite(CompositeType.SUBTRACT, name, builder = builder)
|
composite(CompositeType.SUBTRACT, name, builder = builder)
|
||||||
|
|
||||||
fun VisualGroup3D.intersect(name: String? = null, builder: VisualGroup3D.() -> Unit) =
|
fun VisualGroup3D.intersect(name: String = "", builder: VisualGroup3D.() -> Unit) =
|
||||||
composite(CompositeType.INTERSECT, name, builder = builder)
|
composite(CompositeType.INTERSECT, name, builder = builder)
|
@ -2,11 +2,13 @@
|
|||||||
|
|
||||||
package hep.dataforge.vis.spatial
|
package hep.dataforge.vis.spatial
|
||||||
|
|
||||||
import hep.dataforge.io.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 kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.UseSerializers
|
import kotlinx.serialization.UseSerializers
|
||||||
|
import kotlin.math.cos
|
||||||
|
import kotlin.math.sin
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A cylinder or cut cone segment
|
* A cylinder or cut cone segment
|
||||||
@ -18,7 +20,7 @@ class ConeSegment(
|
|||||||
var upperRadius: Float,
|
var upperRadius: Float,
|
||||||
var startAngle: Float = 0f,
|
var startAngle: Float = 0f,
|
||||||
var angle: Float = PI2
|
var angle: Float = PI2
|
||||||
) : AbstractVisualObject(), VisualObject3D {
|
) : AbstractVisualObject(), VisualObject3D, Shape {
|
||||||
|
|
||||||
@Serializable(ConfigSerializer::class)
|
@Serializable(ConfigSerializer::class)
|
||||||
override var properties: Config? = null
|
override var properties: Config? = null
|
||||||
@ -26,12 +28,55 @@ class ConeSegment(
|
|||||||
override var position: Point3D? = null
|
override var position: Point3D? = null
|
||||||
override var rotation: Point3D? = null
|
override var rotation: Point3D? = null
|
||||||
override var scale: Point3D? = null
|
override var scale: Point3D? = null
|
||||||
|
|
||||||
|
override fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>) {
|
||||||
|
val segments = detail ?: 8
|
||||||
|
require(segments >= 4) { "The number of segments in cone segment is too small" }
|
||||||
|
val angleStep = angle / (segments - 1)
|
||||||
|
|
||||||
|
fun shape(r: Float, z: Float): List<Point3D> {
|
||||||
|
return (0 until segments).map { i ->
|
||||||
|
Point3D(r * cos(startAngle + angleStep * i), r * sin(startAngle + angleStep * i), z)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
geometryBuilder.apply {
|
||||||
|
|
||||||
|
//creating shape in x-y plane with z = 0
|
||||||
|
val bottomOuterPoints = shape(upperRadius, -height / 2)
|
||||||
|
val upperOuterPoints = shape(radius, height / 2)
|
||||||
|
//outer face
|
||||||
|
(1 until segments).forEach {
|
||||||
|
face4(bottomOuterPoints[it - 1], bottomOuterPoints[it], upperOuterPoints[it], upperOuterPoints[it - 1])
|
||||||
|
}
|
||||||
|
|
||||||
|
if (angle == PI2) {
|
||||||
|
face4(bottomOuterPoints.last(), bottomOuterPoints[0], upperOuterPoints[0], upperOuterPoints.last())
|
||||||
|
}
|
||||||
|
|
||||||
|
val zeroBottom = Point3D(0f, 0f, 0f)
|
||||||
|
val zeroTop = Point3D(0f, 0f, height)
|
||||||
|
(1 until segments).forEach {
|
||||||
|
face(bottomOuterPoints[it - 1], zeroBottom, bottomOuterPoints[it])
|
||||||
|
face(upperOuterPoints[it - 1], upperOuterPoints[it], zeroTop)
|
||||||
|
}
|
||||||
|
if (angle == PI2) {
|
||||||
|
face(bottomOuterPoints.last(), zeroBottom, bottomOuterPoints[0])
|
||||||
|
face(upperOuterPoints.last(), upperOuterPoints[0], zeroTop)
|
||||||
|
} else {
|
||||||
|
face4(zeroTop, zeroBottom, bottomOuterPoints[0], upperOuterPoints[0])
|
||||||
|
face4(zeroTop, zeroBottom, bottomOuterPoints.last(), upperOuterPoints.last())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun VisualGroup3D.cylinder(
|
inline fun VisualGroup3D.cylinder(
|
||||||
r: Number,
|
r: Number,
|
||||||
height: Number,
|
height: Number,
|
||||||
name: String? = null,
|
name: String = "",
|
||||||
block: ConeSegment.() -> Unit = {}
|
block: ConeSegment.() -> Unit = {}
|
||||||
): ConeSegment = ConeSegment(
|
): ConeSegment = ConeSegment(
|
||||||
r.toFloat(),
|
r.toFloat(),
|
||||||
|
@ -2,9 +2,8 @@
|
|||||||
|
|
||||||
package hep.dataforge.vis.spatial
|
package hep.dataforge.vis.spatial
|
||||||
|
|
||||||
import hep.dataforge.io.ConfigSerializer
|
import hep.dataforge.io.serialization.ConfigSerializer
|
||||||
import hep.dataforge.meta.Config
|
import hep.dataforge.meta.Config
|
||||||
import hep.dataforge.meta.MetaBuilder
|
|
||||||
import hep.dataforge.vis.common.AbstractVisualObject
|
import hep.dataforge.vis.common.AbstractVisualObject
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.UseSerializers
|
import kotlinx.serialization.UseSerializers
|
||||||
@ -19,19 +18,12 @@ class Convex(val points: List<Point3D>) : AbstractVisualObject(), VisualObject3D
|
|||||||
override var rotation: Point3D? = null
|
override var rotation: Point3D? = null
|
||||||
override var scale: Point3D? = null
|
override var scale: Point3D? = null
|
||||||
|
|
||||||
override fun MetaBuilder.updateMeta() {
|
|
||||||
"points" to {
|
|
||||||
"point" to points.map { it.toMeta() }
|
|
||||||
}
|
|
||||||
updatePosition()
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val TYPE = "geometry.3d.convex"
|
const val TYPE = "geometry.3d.convex"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun VisualGroup3D.convex(name: String? = null, action: ConvexBuilder.() -> Unit = {}) =
|
inline fun VisualGroup3D.convex(name: String = "", action: ConvexBuilder.() -> Unit = {}) =
|
||||||
ConvexBuilder().apply(action).build().also { set(name, it) }
|
ConvexBuilder().apply(action).build().also { set(name, it) }
|
||||||
|
|
||||||
class ConvexBuilder {
|
class ConvexBuilder {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
@file:UseSerializers(Point2DSerializer::class, Point3DSerializer::class)
|
@file:UseSerializers(Point2DSerializer::class, Point3DSerializer::class)
|
||||||
package hep.dataforge.vis.spatial
|
package hep.dataforge.vis.spatial
|
||||||
|
|
||||||
import hep.dataforge.io.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 kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
@ -62,7 +62,7 @@ class Extruded(
|
|||||||
override fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>) {
|
override fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>) {
|
||||||
val shape: Shape2D = shape
|
val shape: Shape2D = shape
|
||||||
|
|
||||||
if (shape.size < 3) error("Extruded shape requires more than points per layer")
|
if (shape.size < 3) error("Extruded shape requires more than 2 points per layer")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expand the shape for specific layers
|
* Expand the shape for specific layers
|
||||||
@ -110,5 +110,5 @@ class Extruded(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun VisualGroup3D.extrude(name: String? = null, action: Extruded.() -> Unit = {}) =
|
fun VisualGroup3D.extrude(name: String = "", action: Extruded.() -> Unit = {}) =
|
||||||
Extruded().apply(action).also { set(name, it) }
|
Extruded().apply(action).also { set(name, it) }
|
@ -0,0 +1,30 @@
|
|||||||
|
@file:UseSerializers(Point3DSerializer::class)
|
||||||
|
|
||||||
|
package hep.dataforge.vis.spatial
|
||||||
|
|
||||||
|
import hep.dataforge.io.serialization.ConfigSerializer
|
||||||
|
import hep.dataforge.meta.Config
|
||||||
|
import hep.dataforge.vis.common.AbstractVisualObject
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.UseSerializers
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class Label3D(var text: String, var fontSize: Double, var fontFamily: String) : AbstractVisualObject(),
|
||||||
|
VisualObject3D {
|
||||||
|
@Serializable(ConfigSerializer::class)
|
||||||
|
override var properties: Config? = null
|
||||||
|
|
||||||
|
override var position: Point3D? = null
|
||||||
|
override var rotation: Point3D? = null
|
||||||
|
override var scale: Point3D? = null
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun VisualGroup3D.label(
|
||||||
|
text: String,
|
||||||
|
fontSize: Number = 20,
|
||||||
|
fontFamily: String = "Arial",
|
||||||
|
name: String = "",
|
||||||
|
action: Label3D.() -> Unit = {}
|
||||||
|
) =
|
||||||
|
Label3D(text, fontSize.toDouble(), fontFamily).apply(action).also { set(name, it) }
|
@ -0,0 +1,96 @@
|
|||||||
|
package hep.dataforge.vis.spatial
|
||||||
|
|
||||||
|
import hep.dataforge.descriptors.NodeDescriptor
|
||||||
|
import hep.dataforge.meta.*
|
||||||
|
import hep.dataforge.names.asName
|
||||||
|
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_OPACITY_KEY
|
||||||
|
|
||||||
|
class Material3D(override val config: Config) : Specific {
|
||||||
|
|
||||||
|
var color by string(key = COLOR_KEY)
|
||||||
|
|
||||||
|
var specularColor by string()
|
||||||
|
|
||||||
|
var opacity by float(1f, key = OPACITY_KEY)
|
||||||
|
|
||||||
|
var wireframe by boolean(false, WIREFRAME_KEY)
|
||||||
|
|
||||||
|
companion object : Specification<Material3D> {
|
||||||
|
override fun wrap(config: Config): Material3D = Material3D(config)
|
||||||
|
|
||||||
|
val MATERIAL_KEY = "material".asName()
|
||||||
|
internal val COLOR_KEY = "color".asName()
|
||||||
|
val MATERIAL_COLOR_KEY = MATERIAL_KEY + COLOR_KEY
|
||||||
|
val SPECULAR_COLOR = "specularColor".asName()
|
||||||
|
internal val OPACITY_KEY = "opacity".asName()
|
||||||
|
val MATERIAL_OPACITY_KEY = MATERIAL_KEY + OPACITY_KEY
|
||||||
|
internal val WIREFRAME_KEY = "wireframe".asName()
|
||||||
|
val MATERIAL_WIREFRAME_KEY = MATERIAL_KEY + WIREFRAME_KEY
|
||||||
|
|
||||||
|
val descriptor = NodeDescriptor {
|
||||||
|
value(VisualObject3D.VISIBLE_KEY) {
|
||||||
|
type(ValueType.BOOLEAN)
|
||||||
|
default(true)
|
||||||
|
}
|
||||||
|
node(MATERIAL_KEY) {
|
||||||
|
value(COLOR_KEY) {
|
||||||
|
type(ValueType.STRING, ValueType.NUMBER)
|
||||||
|
default("#ffffff")
|
||||||
|
}
|
||||||
|
value(OPACITY_KEY) {
|
||||||
|
type(ValueType.NUMBER)
|
||||||
|
default(1.0)
|
||||||
|
}
|
||||||
|
value(WIREFRAME_KEY) {
|
||||||
|
type(ValueType.BOOLEAN)
|
||||||
|
default(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun VisualObject3D.color(rgb: String) {
|
||||||
|
setProperty(MATERIAL_COLOR_KEY, rgb)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun VisualObject3D.color(rgb: Int) {
|
||||||
|
setProperty(MATERIAL_COLOR_KEY, rgb)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun VisualObject3D.color(r: UByte, g: UByte, b: UByte) = setProperty(
|
||||||
|
MATERIAL_COLOR_KEY,
|
||||||
|
Colors.rgbToMeta(r, g, b)
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web colors representation of the color in `#rrggbb` format or HTML name
|
||||||
|
*/
|
||||||
|
var VisualObject3D.color: String?
|
||||||
|
get() = getProperty(MATERIAL_COLOR_KEY)?.let { Colors.fromMeta(it) }
|
||||||
|
set(value) {
|
||||||
|
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)
|
||||||
|
|
||||||
|
fun VisualObject3D.material(builder: Material3D.() -> Unit) {
|
||||||
|
val node = config[Material3D.MATERIAL_KEY].node
|
||||||
|
if (node != null) {
|
||||||
|
Material3D.update(node, builder)
|
||||||
|
} else {
|
||||||
|
config[Material3D.MATERIAL_KEY] = Material3D(builder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var VisualObject3D.opacity: Double?
|
||||||
|
get() = getProperty(MATERIAL_OPACITY_KEY).double
|
||||||
|
set(value) {
|
||||||
|
setProperty(MATERIAL_OPACITY_KEY, value)
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
@file:UseSerializers(Point3DSerializer::class)
|
||||||
|
|
||||||
|
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.number
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.UseSerializers
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class PolyLine(var points: List<Point3D>) : AbstractVisualObject(), VisualObject3D {
|
||||||
|
@Serializable(ConfigSerializer::class)
|
||||||
|
override var properties: Config? = null
|
||||||
|
|
||||||
|
override var position: Point3D? = null
|
||||||
|
override var rotation: Point3D? = null
|
||||||
|
override var scale: Point3D? = null
|
||||||
|
|
||||||
|
//var lineType by string()
|
||||||
|
var thickness by number(1.0, key = "material.thickness")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun VisualGroup3D.polyline(vararg points: Point3D, name: String = "", action: PolyLine.() -> Unit = {}) =
|
||||||
|
PolyLine(points.toList()).apply(action).also { set(name, it) }
|
@ -1,22 +1,32 @@
|
|||||||
@file:UseSerializers(Point3DSerializer::class, NameSerializer::class)
|
@file:UseSerializers(Point3DSerializer::class, NameSerializer::class, ConfigSerializer::class)
|
||||||
|
|
||||||
package hep.dataforge.vis.spatial
|
package hep.dataforge.vis.spatial
|
||||||
|
|
||||||
import hep.dataforge.io.ConfigSerializer
|
import hep.dataforge.io.serialization.ConfigSerializer
|
||||||
import hep.dataforge.io.NameSerializer
|
import hep.dataforge.io.serialization.NameSerializer
|
||||||
import hep.dataforge.meta.Config
|
import hep.dataforge.meta.Config
|
||||||
import hep.dataforge.meta.MetaBuilder
|
import hep.dataforge.meta.Laminate
|
||||||
import hep.dataforge.meta.MetaItem
|
import hep.dataforge.meta.MetaItem
|
||||||
|
import hep.dataforge.meta.get
|
||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.Name
|
||||||
import hep.dataforge.vis.common.AbstractVisualObject
|
import hep.dataforge.names.NameToken
|
||||||
|
import hep.dataforge.names.asName
|
||||||
|
import hep.dataforge.names.plus
|
||||||
|
import hep.dataforge.vis.common.*
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.Transient
|
||||||
import kotlinx.serialization.UseSerializers
|
import kotlinx.serialization.UseSerializers
|
||||||
|
import kotlin.collections.component1
|
||||||
|
import kotlin.collections.component2
|
||||||
|
import kotlin.collections.set
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A proxy [VisualObject3D] to reuse a template object
|
* A proxy [VisualObject3D] to reuse a template object
|
||||||
*/
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
class Proxy(val templateName: Name) : AbstractVisualObject(), VisualObject3D {
|
@SerialName("3d.proxy")
|
||||||
|
class Proxy(val templateName: Name) : AbstractVisualObject(), VisualGroup, VisualObject3D {
|
||||||
|
|
||||||
override var position: Point3D? = null
|
override var position: Point3D? = null
|
||||||
override var rotation: Point3D? = null
|
override var rotation: Point3D? = null
|
||||||
@ -28,36 +38,133 @@ class Proxy(val templateName: Name) : AbstractVisualObject(), VisualObject3D {
|
|||||||
/**
|
/**
|
||||||
* Recursively search for defined template in the parent
|
* Recursively search for defined template in the parent
|
||||||
*/
|
*/
|
||||||
val template: VisualObject3D
|
val prototype: VisualObject3D
|
||||||
get() = (parent as? VisualGroup3D)?.getTemplate(templateName)
|
get() = (parent as? VisualGroup3D)?.getPrototype(templateName)
|
||||||
?: error("Template with name $templateName not found in $parent")
|
?: error("Template with name $templateName not found in $parent")
|
||||||
|
|
||||||
|
override val styleSheet: StyleSheet
|
||||||
|
get() = (parent as? VisualGroup)?.styleSheet ?: StyleSheet(this)
|
||||||
|
|
||||||
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? {
|
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? {
|
||||||
return if (inherit) {
|
return if (inherit) {
|
||||||
super.getProperty(name, false) ?: template.getProperty(name, false) ?: parent?.getProperty(name, inherit)
|
properties?.get(name)
|
||||||
|
?: mergedStyles[name]
|
||||||
|
?: prototype.getProperty(name)
|
||||||
|
?: parent?.getProperty(name)
|
||||||
} else {
|
} else {
|
||||||
super.getProperty(name, false) ?: template.getProperty(name, false)
|
properties?.get(name)
|
||||||
|
?: mergedStyles[name]
|
||||||
|
?: prototype.getProperty(name, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun MetaBuilder.updateMeta() {
|
override val children: Map<NameToken, ProxyChild>
|
||||||
//TODO add reference to child
|
get() = (prototype as? MutableVisualGroup)?.children
|
||||||
updatePosition()
|
?.filter { !it.key.toString().startsWith("@") }
|
||||||
|
?.mapValues {
|
||||||
|
ProxyChild(it.key.asName())
|
||||||
|
} ?: emptyMap()
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
private val propertyCache: HashMap<Name, Config> = HashMap()
|
||||||
|
|
||||||
|
fun childPropertyName(childName: Name, propertyName: Name): Name {
|
||||||
|
return NameToken(PROXY_CHILD_PROPERTY_PREFIX, childName.toString()) + propertyName
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prototypeFor(name: Name): VisualObject {
|
||||||
|
return (prototype as? VisualGroup)?.get(name)
|
||||||
|
?: error("Prototype with name $name not found in $this")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun allProperties(): Laminate = Laminate(properties, mergedStyles, prototype.allProperties())
|
||||||
|
|
||||||
|
//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)
|
||||||
|
|
||||||
|
override val styleSheet: StyleSheet get() = this@Proxy.styleSheet
|
||||||
|
|
||||||
|
override val children: Map<NameToken, VisualObject>
|
||||||
|
get() = (prototype as? VisualGroup)?.children?.mapValues { (key, _) ->
|
||||||
|
ProxyChild(
|
||||||
|
name + key.asName()
|
||||||
|
)
|
||||||
|
} ?: emptyMap()
|
||||||
|
|
||||||
|
override var properties: Config?
|
||||||
|
get() = propertyCache[name]
|
||||||
|
set(value) {
|
||||||
|
if (value == null) {
|
||||||
|
propertyCache.remove(name)?.also {
|
||||||
|
//Removing listener if it is present
|
||||||
|
removeChangeListener(this@Proxy)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
propertyCache[name] = value.also {
|
||||||
|
onPropertyChange(this@Proxy) { propertyName, before, after ->
|
||||||
|
this@Proxy.propertyChanged(childPropertyName(name, propertyName), before, after)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? {
|
||||||
|
return if (inherit) {
|
||||||
|
properties?.get(name)
|
||||||
|
?: mergedStyles[name]
|
||||||
|
?: prototype.getProperty(name)
|
||||||
|
?: parent?.getProperty(name)
|
||||||
|
} else {
|
||||||
|
properties?.get(name)
|
||||||
|
?: mergedStyles[name]
|
||||||
|
?: prototype.getProperty(name, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun allProperties(): Laminate = Laminate(properties, mergedStyles, prototype.allProperties())
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val PROXY_CHILD_PROPERTY_PREFIX = "@child"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//fun VisualGroup3D.proxy(
|
val VisualObject.prototype: VisualObject
|
||||||
// templateName: Name,
|
get() = when (this) {
|
||||||
// //name: String? = null,
|
is Proxy -> prototype
|
||||||
// builder: VisualGroup3D.() -> Unit
|
is Proxy.ProxyChild -> prototype
|
||||||
//): Proxy {
|
else -> this
|
||||||
// val template = getTemplate(templateName) ?: templates.builder()
|
}
|
||||||
// return Proxy(this, templateName).also { set(name, it) }
|
|
||||||
//}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create ref for existing prototype
|
||||||
|
*/
|
||||||
inline fun VisualGroup3D.ref(
|
inline fun VisualGroup3D.ref(
|
||||||
templateName: Name,
|
templateName: Name,
|
||||||
name: String? = null,
|
name: String = "",
|
||||||
action: Proxy.() -> Unit = {}
|
block: Proxy.() -> Unit = {}
|
||||||
) = Proxy(templateName).apply(action).also { set(name, it) }
|
) = Proxy(templateName).apply(block).also { set(name, it) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add new proxy wrapping given object and automatically adding it to the prototypes
|
||||||
|
*/
|
||||||
|
fun VisualGroup3D.proxy(
|
||||||
|
templateName: Name,
|
||||||
|
obj: VisualObject3D,
|
||||||
|
name: String = "",
|
||||||
|
block: Proxy.() -> Unit = {}
|
||||||
|
): Proxy {
|
||||||
|
val existing = getPrototype(templateName)
|
||||||
|
if (existing == null) {
|
||||||
|
setPrototype(templateName, obj)
|
||||||
|
} else if (existing != obj) {
|
||||||
|
error("Can't add different prototype on top of existing one")
|
||||||
|
}
|
||||||
|
return ref(templateName, name, block)
|
||||||
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
package hep.dataforge.vis.spatial
|
package hep.dataforge.vis.spatial
|
||||||
|
|
||||||
import hep.dataforge.io.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 kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
@ -16,7 +16,7 @@ class Sphere(
|
|||||||
var phi: Float = PI2,
|
var phi: Float = PI2,
|
||||||
var thetaStart: Float = 0f,
|
var thetaStart: Float = 0f,
|
||||||
var theta: Float = PI.toFloat()
|
var theta: Float = PI.toFloat()
|
||||||
) : AbstractVisualObject(), VisualObject3D {
|
) : AbstractVisualObject(), VisualObject3D, Shape {
|
||||||
|
|
||||||
@Serializable(ConfigSerializer::class)
|
@Serializable(ConfigSerializer::class)
|
||||||
override var properties: Config? = null
|
override var properties: Config? = null
|
||||||
@ -24,13 +24,26 @@ class Sphere(
|
|||||||
override var position: Point3D? = null
|
override var position: Point3D? = null
|
||||||
override var rotation: Point3D? = null
|
override var rotation: Point3D? = null
|
||||||
override var scale: Point3D? = null
|
override var scale: Point3D? = null
|
||||||
|
|
||||||
|
override fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>) {
|
||||||
|
TODO("not implemented")
|
||||||
|
// val segments = this.detail ?: 8
|
||||||
|
// require(segments >= 4) { "The detail for sphere must be >= 4" }
|
||||||
|
// val phiStep = phi / segments
|
||||||
|
// val thetaStep = theta / segments
|
||||||
|
// for (i in 1 until segments - 1) {
|
||||||
|
// for (j in 0 until segments - 1) {
|
||||||
|
// val point1 = Point3D()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun VisualGroup3D.sphere(
|
inline fun VisualGroup3D.sphere(
|
||||||
radius: Number,
|
radius: Number,
|
||||||
phi: Number = 2 * PI,
|
phi: Number = 2 * PI,
|
||||||
theta: Number = PI,
|
theta: Number = PI,
|
||||||
name: String? = null,
|
name: String = "",
|
||||||
action: Sphere.() -> Unit = {}
|
action: Sphere.() -> Unit = {}
|
||||||
) = Sphere(
|
) = Sphere(
|
||||||
radius.toFloat(),
|
radius.toFloat(),
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
@file:UseSerializers(Point3DSerializer::class)
|
@file:UseSerializers(Point3DSerializer::class)
|
||||||
package hep.dataforge.vis.spatial
|
package hep.dataforge.vis.spatial
|
||||||
|
|
||||||
import hep.dataforge.io.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 kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
@ -123,6 +123,7 @@ class Tube(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun VisualGroup3D.tube(
|
inline fun VisualGroup3D.tube(
|
||||||
@ -131,7 +132,7 @@ inline fun VisualGroup3D.tube(
|
|||||||
innerRadius: Number = 0f,
|
innerRadius: Number = 0f,
|
||||||
startAngle: Number = 0f,
|
startAngle: Number = 0f,
|
||||||
angle: Number = 2 * PI,
|
angle: Number = 2 * PI,
|
||||||
name: String? = null,
|
name: String = "",
|
||||||
block: Tube.() -> Unit = {}
|
block: Tube.() -> Unit = {}
|
||||||
): Tube = Tube(
|
): Tube = Tube(
|
||||||
r.toFloat(),
|
r.toFloat(),
|
||||||
|
@ -1,14 +1,20 @@
|
|||||||
package hep.dataforge.vis.spatial
|
package hep.dataforge.vis.spatial
|
||||||
|
|
||||||
import hep.dataforge.context.AbstractPlugin
|
import hep.dataforge.context.AbstractPlugin
|
||||||
|
import hep.dataforge.context.Context
|
||||||
import hep.dataforge.context.PluginFactory
|
import hep.dataforge.context.PluginFactory
|
||||||
import hep.dataforge.context.PluginTag
|
import hep.dataforge.context.PluginTag
|
||||||
|
import hep.dataforge.io.serialization.ConfigSerializer
|
||||||
|
import hep.dataforge.io.serialization.MetaSerializer
|
||||||
|
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.vis.common.VisualObject
|
||||||
import hep.dataforge.vis.common.VisualPlugin
|
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 kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
class Visual3DPlugin(meta: Meta) : AbstractPlugin(meta) {
|
class Visual3DPlugin(meta: Meta) : AbstractPlugin(meta) {
|
||||||
@ -25,16 +31,26 @@ class Visual3DPlugin(meta: Meta) : AbstractPlugin(meta) {
|
|||||||
companion object : PluginFactory<Visual3DPlugin> {
|
companion object : PluginFactory<Visual3DPlugin> {
|
||||||
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 Visual3DPlugin> = Visual3DPlugin::class
|
||||||
override fun invoke(meta: Meta): Visual3DPlugin = Visual3DPlugin(meta)
|
override fun invoke(meta: Meta, context: Context): Visual3DPlugin = Visual3DPlugin(meta)
|
||||||
|
|
||||||
val serialModule = SerializersModule {
|
val serialModule = SerializersModule {
|
||||||
polymorphic(VisualObject3D::class) {
|
contextual(Point3DSerializer)
|
||||||
|
contextual(Point2DSerializer)
|
||||||
|
contextual(NameSerializer)
|
||||||
|
contextual(NameTokenSerializer)
|
||||||
|
contextual(MetaSerializer)
|
||||||
|
contextual(ConfigSerializer)
|
||||||
|
|
||||||
|
polymorphic(VisualObject::class, VisualObject3D::class) {
|
||||||
VisualGroup3D::class with VisualGroup3D.serializer()
|
VisualGroup3D::class with VisualGroup3D.serializer()
|
||||||
Proxy::class with Proxy.serializer()
|
Proxy::class with Proxy.serializer()
|
||||||
Composite::class with Composite.serializer()
|
Composite::class with Composite.serializer()
|
||||||
Tube::class with Tube.serializer()
|
Tube::class with Tube.serializer()
|
||||||
Box::class with Box.serializer()
|
Box::class with Box.serializer()
|
||||||
Convex::class with Convex.serializer()
|
Convex::class with Convex.serializer()
|
||||||
|
Extruded::class with Extruded.serializer()
|
||||||
|
addSubclass(PolyLine.serializer())
|
||||||
|
addSubclass(Label3D.serializer())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,143 @@
|
|||||||
|
@file:UseSerializers(
|
||||||
|
Point3DSerializer::class,
|
||||||
|
ConfigSerializer::class,
|
||||||
|
NameTokenSerializer::class,
|
||||||
|
NameSerializer::class,
|
||||||
|
MetaSerializer::class
|
||||||
|
)
|
||||||
|
|
||||||
|
package hep.dataforge.vis.spatial
|
||||||
|
|
||||||
|
import hep.dataforge.io.serialization.ConfigSerializer
|
||||||
|
import hep.dataforge.io.serialization.MetaSerializer
|
||||||
|
import hep.dataforge.io.serialization.NameSerializer
|
||||||
|
import hep.dataforge.meta.Config
|
||||||
|
import hep.dataforge.names.Name
|
||||||
|
import hep.dataforge.names.NameToken
|
||||||
|
import hep.dataforge.names.asName
|
||||||
|
import hep.dataforge.names.isEmpty
|
||||||
|
import hep.dataforge.vis.common.AbstractVisualGroup
|
||||||
|
import hep.dataforge.vis.common.StyleSheet
|
||||||
|
import hep.dataforge.vis.common.VisualObject
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.UseSerializers
|
||||||
|
import kotlin.collections.set
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents 3-dimensional Visual Group
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
@SerialName("group.3d")
|
||||||
|
class VisualGroup3D : AbstractVisualGroup(), VisualObject3D {
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
|
||||||
|
override var position: Point3D? = null
|
||||||
|
override var rotation: Point3D? = null
|
||||||
|
override var scale: Point3D? = null
|
||||||
|
|
||||||
|
@SerialName("children")
|
||||||
|
private val _children = HashMap<NameToken, VisualObject>()
|
||||||
|
override val children: Map<NameToken, VisualObject> get() = _children
|
||||||
|
|
||||||
|
init {
|
||||||
|
//Do after deserialization
|
||||||
|
attachChildren()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update or create stylesheet
|
||||||
|
*/
|
||||||
|
fun styleSheet(block: StyleSheet.() -> Unit) {
|
||||||
|
val res = this.styleSheet ?: StyleSheet(this).also { this.styleSheet = it }
|
||||||
|
res.block()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeChild(token: NameToken) {
|
||||||
|
_children.remove(token)
|
||||||
|
childrenChanged(token.asName(), null)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setChild(token: NameToken, child: VisualObject) {
|
||||||
|
if (child.parent == null) {
|
||||||
|
child.parent = this
|
||||||
|
} else {
|
||||||
|
error("Can't reassign existing parent for $child")
|
||||||
|
}
|
||||||
|
_children[token] = child
|
||||||
|
childrenChanged(token.asName(), child)
|
||||||
|
}
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * TODO add special static group to hold statics without propagation
|
||||||
|
// */
|
||||||
|
// override fun addStatic(child: VisualObject) = setChild(NameToken("@static(${child.hashCode()})"), child)
|
||||||
|
|
||||||
|
override fun createGroup(name: Name): VisualGroup3D {
|
||||||
|
return when {
|
||||||
|
name.isEmpty() -> error("Should be unreachable")
|
||||||
|
name.length == 1 -> {
|
||||||
|
val token = name.first()!!
|
||||||
|
when (val current = children[token]) {
|
||||||
|
null -> VisualGroup3D().also { setChild(token, it) }
|
||||||
|
is VisualGroup3D -> current
|
||||||
|
else -> error("Can't create group with name $name because it exists and not a group")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> createGroup(name.first()!!.asName()).createGroup(name.cutFirst())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun attachChildren() {
|
||||||
|
super.attachChildren()
|
||||||
|
prototypes?.run {
|
||||||
|
parent = this
|
||||||
|
attachChildren()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val PROTOTYPES_KEY = "templates"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ger a prototype redirecting the request to the parent if prototype is not found
|
||||||
|
*/
|
||||||
|
fun VisualGroup3D.getPrototype(name: Name): VisualObject3D? =
|
||||||
|
prototypes?.get(name) as? VisualObject3D ?: (parent as? VisualGroup3D)?.getPrototype(name)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defined a prototype inside current group
|
||||||
|
*/
|
||||||
|
fun VisualGroup3D.setPrototype(name: Name, obj: VisualObject3D) {
|
||||||
|
(prototypes ?: VisualGroup3D().also { this.prototypes = it })[name] = obj
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
}
|
||||||
|
|
@ -1,22 +1,22 @@
|
|||||||
@file:UseSerializers(Point3DSerializer::class, NameSerializer::class)
|
@file:UseSerializers(Point3DSerializer::class, NameSerializer::class, NameTokenSerializer::class)
|
||||||
|
|
||||||
package hep.dataforge.vis.spatial
|
package hep.dataforge.vis.spatial
|
||||||
|
|
||||||
import hep.dataforge.io.ConfigSerializer
|
import hep.dataforge.io.serialization.NameSerializer
|
||||||
import hep.dataforge.io.NameSerializer
|
|
||||||
import hep.dataforge.meta.*
|
import hep.dataforge.meta.*
|
||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.asName
|
||||||
import hep.dataforge.names.plus
|
import hep.dataforge.names.plus
|
||||||
import hep.dataforge.output.Output
|
import hep.dataforge.output.Renderer
|
||||||
import hep.dataforge.vis.common.VisualGroup
|
|
||||||
import hep.dataforge.vis.common.VisualObject
|
import hep.dataforge.vis.common.VisualObject
|
||||||
import hep.dataforge.vis.common.asName
|
|
||||||
import hep.dataforge.vis.spatial.VisualObject3D.Companion.DETAIL_KEY
|
import hep.dataforge.vis.spatial.VisualObject3D.Companion.DETAIL_KEY
|
||||||
import hep.dataforge.vis.spatial.VisualObject3D.Companion.MATERIAL_KEY
|
import hep.dataforge.vis.spatial.VisualObject3D.Companion.IGNORE_KEY
|
||||||
|
import hep.dataforge.vis.spatial.VisualObject3D.Companion.LAYER_KEY
|
||||||
import hep.dataforge.vis.spatial.VisualObject3D.Companion.VISIBLE_KEY
|
import hep.dataforge.vis.spatial.VisualObject3D.Companion.VISIBLE_KEY
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import kotlinx.serialization.UseSerializers
|
import kotlinx.serialization.UseSerializers
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for 3-dimensional [VisualObject]
|
||||||
|
*/
|
||||||
interface VisualObject3D : VisualObject {
|
interface VisualObject3D : VisualObject {
|
||||||
var position: Point3D?
|
var position: Point3D?
|
||||||
var rotation: Point3D?
|
var rotation: Point3D?
|
||||||
@ -35,9 +35,14 @@ interface VisualObject3D : VisualObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val MATERIAL_KEY = "material".asName()
|
|
||||||
val VISIBLE_KEY = "visible".asName()
|
val VISIBLE_KEY = "visible".asName()
|
||||||
|
val SELECTED_KEY = "selected".asName()
|
||||||
val DETAIL_KEY = "detail".asName()
|
val DETAIL_KEY = "detail".asName()
|
||||||
|
val LAYER_KEY = "layer".asName()
|
||||||
|
val IGNORE_KEY = "ignore".asName()
|
||||||
|
|
||||||
|
val GEOMETRY_KEY = "geometry".asName()
|
||||||
|
|
||||||
val x = "x".asName()
|
val x = "x".asName()
|
||||||
val y = "y".asName()
|
val y = "y".asName()
|
||||||
@ -65,44 +70,16 @@ interface VisualObject3D : VisualObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
/**
|
||||||
class VisualGroup3D : VisualGroup<VisualObject3D>(), VisualObject3D, Configurable {
|
* Count number of layers to the top object. Return 1 if this is top layer
|
||||||
/**
|
|
||||||
* A container for templates visible inside this group
|
|
||||||
*/
|
*/
|
||||||
var templates: VisualGroup3D? = null
|
var VisualObject3D.layer: Int
|
||||||
|
get() = getProperty(LAYER_KEY).int ?: 0
|
||||||
set(value) {
|
set(value) {
|
||||||
value?.parent = this
|
setProperty(LAYER_KEY, value)
|
||||||
field = value
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable(ConfigSerializer::class)
|
fun Renderer<VisualObject3D>.render(meta: Meta = EmptyMeta, action: VisualGroup3D.() -> Unit) =
|
||||||
override var properties: Config? = null
|
|
||||||
|
|
||||||
override var position: Point3D? = null
|
|
||||||
override var rotation: Point3D? = null
|
|
||||||
override var scale: Point3D? = null
|
|
||||||
|
|
||||||
override val namedChildren: MutableMap<Name, VisualObject3D> = HashMap()
|
|
||||||
override val unnamedChildren: MutableList<VisualObject3D> = ArrayList()
|
|
||||||
|
|
||||||
fun getTemplate(name: Name): VisualObject3D? = templates?.get(name) ?: (parent as? VisualGroup3D)?.getTemplate(name)
|
|
||||||
|
|
||||||
override fun MetaBuilder.updateMeta() {
|
|
||||||
set(TEMPLATES_KEY, templates?.toMeta())
|
|
||||||
updatePosition()
|
|
||||||
updateChildren()
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val TEMPLATES_KEY = "templates"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun VisualGroup3D.group(key: String? = null, action: VisualGroup3D.() -> Unit = {}): VisualGroup3D =
|
|
||||||
VisualGroup3D().apply(action).also { set(key, it) }
|
|
||||||
|
|
||||||
fun Output<VisualObject3D>.render(meta: Meta = EmptyMeta, action: VisualGroup3D.() -> Unit) =
|
|
||||||
render(VisualGroup3D().apply(action), meta)
|
render(VisualGroup3D().apply(action), meta)
|
||||||
|
|
||||||
// Common properties
|
// Common properties
|
||||||
@ -131,27 +108,21 @@ var VisualObject3D.detail: Int?
|
|||||||
get() = getProperty(DETAIL_KEY, false).int
|
get() = getProperty(DETAIL_KEY, false).int
|
||||||
set(value) = setProperty(DETAIL_KEY, value)
|
set(value) = setProperty(DETAIL_KEY, value)
|
||||||
|
|
||||||
var VisualObject3D.material: Meta?
|
var VisualObject.visible: Boolean?
|
||||||
get() = getProperty(MATERIAL_KEY).node
|
|
||||||
set(value) = setProperty(MATERIAL_KEY, value)
|
|
||||||
|
|
||||||
var VisualObject3D.visible: Boolean?
|
|
||||||
get() = getProperty(VISIBLE_KEY).boolean
|
get() = getProperty(VISIBLE_KEY).boolean
|
||||||
set(value) = setProperty(VISIBLE_KEY, value)
|
set(value) = setProperty(VISIBLE_KEY, value)
|
||||||
|
|
||||||
fun VisualObject3D.color(rgb: Int) {
|
/**
|
||||||
material = buildMeta { "color" to rgb }
|
* If this property is true, the object will be ignored on render.
|
||||||
}
|
* Property is not inherited.
|
||||||
|
*/
|
||||||
|
var VisualObject.ignore: Boolean?
|
||||||
|
get() = getProperty(IGNORE_KEY,false).boolean
|
||||||
|
set(value) = setProperty(IGNORE_KEY, value)
|
||||||
|
|
||||||
fun VisualObject3D.material(builder: MetaBuilder.() -> Unit) {
|
//var VisualObject.selected: Boolean?
|
||||||
material = buildMeta(builder)
|
// get() = getProperty(SELECTED_KEY).boolean
|
||||||
}
|
// set(value) = setProperty(SELECTED_KEY, value)
|
||||||
|
|
||||||
fun VisualObject3D.color(r: Int, g: Int, b: Int) = material {
|
|
||||||
"red" to r
|
|
||||||
"green" to g
|
|
||||||
"blue" to b
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun VisualObject3D.position(): Point3D =
|
private fun VisualObject3D.position(): Point3D =
|
||||||
position ?: Point3D(0.0, 0.0, 0.0).also { position = it }
|
position ?: Point3D(0.0, 0.0, 0.0).also { position = it }
|
||||||
@ -160,21 +131,21 @@ var VisualObject3D.x: Number
|
|||||||
get() = position?.x ?: 0f
|
get() = position?.x ?: 0f
|
||||||
set(value) {
|
set(value) {
|
||||||
position().x = value.toDouble()
|
position().x = value.toDouble()
|
||||||
propertyChanged(VisualObject3D.xPos)
|
propertyInvalidated(VisualObject3D.xPos)
|
||||||
}
|
}
|
||||||
|
|
||||||
var VisualObject3D.y: Number
|
var VisualObject3D.y: Number
|
||||||
get() = position?.y ?: 0f
|
get() = position?.y ?: 0f
|
||||||
set(value) {
|
set(value) {
|
||||||
position().y = value.toDouble()
|
position().y = value.toDouble()
|
||||||
propertyChanged(VisualObject3D.yPos)
|
propertyInvalidated(VisualObject3D.yPos)
|
||||||
}
|
}
|
||||||
|
|
||||||
var VisualObject3D.z: Number
|
var VisualObject3D.z: Number
|
||||||
get() = position?.z ?: 0f
|
get() = position?.z ?: 0f
|
||||||
set(value) {
|
set(value) {
|
||||||
position().z = value.toDouble()
|
position().z = value.toDouble()
|
||||||
propertyChanged(VisualObject3D.zPos)
|
propertyInvalidated(VisualObject3D.zPos)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun VisualObject3D.rotation(): Point3D =
|
private fun VisualObject3D.rotation(): Point3D =
|
||||||
@ -184,21 +155,21 @@ var VisualObject3D.rotationX: Number
|
|||||||
get() = rotation?.x ?: 0f
|
get() = rotation?.x ?: 0f
|
||||||
set(value) {
|
set(value) {
|
||||||
rotation().x = value.toDouble()
|
rotation().x = value.toDouble()
|
||||||
propertyChanged(VisualObject3D.xRotation)
|
propertyInvalidated(VisualObject3D.xRotation)
|
||||||
}
|
}
|
||||||
|
|
||||||
var VisualObject3D.rotationY: Number
|
var VisualObject3D.rotationY: Number
|
||||||
get() = rotation?.y ?: 0f
|
get() = rotation?.y ?: 0f
|
||||||
set(value) {
|
set(value) {
|
||||||
rotation().y = value.toDouble()
|
rotation().y = value.toDouble()
|
||||||
propertyChanged(VisualObject3D.yRotation)
|
propertyInvalidated(VisualObject3D.yRotation)
|
||||||
}
|
}
|
||||||
|
|
||||||
var VisualObject3D.rotationZ: Number
|
var VisualObject3D.rotationZ: Number
|
||||||
get() = rotation?.z ?: 0f
|
get() = rotation?.z ?: 0f
|
||||||
set(value) {
|
set(value) {
|
||||||
rotation().z = value.toDouble()
|
rotation().z = value.toDouble()
|
||||||
propertyChanged(VisualObject3D.zRotation)
|
propertyInvalidated(VisualObject3D.zRotation)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun VisualObject3D.scale(): Point3D =
|
private fun VisualObject3D.scale(): Point3D =
|
||||||
@ -208,19 +179,19 @@ var VisualObject3D.scaleX: Number
|
|||||||
get() = scale?.x ?: 1f
|
get() = scale?.x ?: 1f
|
||||||
set(value) {
|
set(value) {
|
||||||
scale().x = value.toDouble()
|
scale().x = value.toDouble()
|
||||||
propertyChanged(VisualObject3D.xScale)
|
propertyInvalidated(VisualObject3D.xScale)
|
||||||
}
|
}
|
||||||
|
|
||||||
var VisualObject3D.scaleY: Number
|
var VisualObject3D.scaleY: Number
|
||||||
get() = scale?.y ?: 1f
|
get() = scale?.y ?: 1f
|
||||||
set(value) {
|
set(value) {
|
||||||
scale().y = value.toDouble()
|
scale().y = value.toDouble()
|
||||||
propertyChanged(VisualObject3D.yScale)
|
propertyInvalidated(VisualObject3D.yScale)
|
||||||
}
|
}
|
||||||
|
|
||||||
var VisualObject3D.scaleZ: Number
|
var VisualObject3D.scaleZ: Number
|
||||||
get() = scale?.z ?: 1f
|
get() = scale?.z ?: 1f
|
||||||
set(value) {
|
set(value) {
|
||||||
scale().z = value.toDouble()
|
scale().z = value.toDouble()
|
||||||
propertyChanged(VisualObject3D.zScale)
|
propertyInvalidated(VisualObject3D.zScale)
|
||||||
}
|
}
|
@ -3,12 +3,8 @@ package hep.dataforge.vis.spatial
|
|||||||
import kotlin.math.PI
|
import kotlin.math.PI
|
||||||
|
|
||||||
object World {
|
object World {
|
||||||
const val CAMERA_INITIAL_DISTANCE = -500.0
|
val ZERO = Point3D(0.0, 0.0, 0.0)
|
||||||
const val CAMERA_INITIAL_X_ANGLE = -50.0
|
val ONE = Point3D(1.0, 1.0, 1.0)
|
||||||
const val CAMERA_INITIAL_Y_ANGLE = 0.0
|
|
||||||
const val CAMERA_INITIAL_Z_ANGLE = -210.0
|
|
||||||
const val CAMERA_NEAR_CLIP = 0.1
|
|
||||||
const val CAMERA_FAR_CLIP = 10000.0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const val PI2: Float = 2 * PI.toFloat()
|
const val PI2: Float = 2 * PI.toFloat()
|
@ -14,8 +14,8 @@ operator fun Point2D.component1() = x
|
|||||||
operator fun Point2D.component2() = y
|
operator fun Point2D.component2() = y
|
||||||
|
|
||||||
fun Point2D.toMeta() = buildMeta {
|
fun Point2D.toMeta() = buildMeta {
|
||||||
VisualObject3D.x to x
|
VisualObject3D.x put x
|
||||||
VisualObject3D.y to y
|
VisualObject3D.y put y
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Meta.point2D() = Point2D(this["x"].number ?: 0, this["y"].number ?: 0)
|
fun Meta.point2D() = Point2D(this["x"].number ?: 0, this["y"].number ?: 0)
|
||||||
@ -26,16 +26,16 @@ expect class Point3D(x: Number, y: Number, z: Number) {
|
|||||||
var z: Double
|
var z: Double
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expect operator fun Point3D.plus(other: Point3D): Point3D
|
||||||
|
|
||||||
operator fun Point3D.component1() = x
|
operator fun Point3D.component1() = x
|
||||||
operator fun Point3D.component2() = y
|
operator fun Point3D.component2() = y
|
||||||
operator fun Point3D.component3() = z
|
operator fun Point3D.component3() = z
|
||||||
|
|
||||||
fun Meta.point3D() = Point3D(this["x"].number ?: 0, this["y"].number ?: 0, this["y"].number ?: 0)
|
fun Meta.point3D() = Point3D(this["x"].number ?: 0, this["y"].number ?: 0, this["y"].number ?: 0)
|
||||||
|
|
||||||
val zero = Point3D(0, 0, 0)
|
|
||||||
|
|
||||||
fun Point3D.toMeta() = buildMeta {
|
fun Point3D.toMeta() = buildMeta {
|
||||||
VisualObject3D.x to x
|
VisualObject3D.x put x
|
||||||
VisualObject3D.y to y
|
VisualObject3D.y put y
|
||||||
VisualObject3D.z to z
|
VisualObject3D.z put z
|
||||||
}
|
}
|
@ -1,41 +1,109 @@
|
|||||||
package hep.dataforge.vis.spatial
|
package hep.dataforge.vis.spatial
|
||||||
|
|
||||||
|
import hep.dataforge.io.serialization.descriptor
|
||||||
|
import hep.dataforge.names.NameToken
|
||||||
|
import hep.dataforge.names.toName
|
||||||
import kotlinx.serialization.*
|
import kotlinx.serialization.*
|
||||||
|
import kotlinx.serialization.internal.DoubleSerializer
|
||||||
|
import kotlinx.serialization.internal.StringDescriptor
|
||||||
|
import kotlinx.serialization.internal.nullable
|
||||||
|
|
||||||
@Serializable
|
inline fun <R> Decoder.decodeStructure(
|
||||||
private data class Point2DSerial(val x: Double, val y: Double)
|
desc: SerialDescriptor,
|
||||||
|
vararg typeParams: KSerializer<*> = emptyArray(),
|
||||||
|
crossinline block: CompositeDecoder.() -> R
|
||||||
|
): R {
|
||||||
|
val decoder = beginStructure(desc, *typeParams)
|
||||||
|
val res = decoder.block()
|
||||||
|
decoder.endStructure(desc)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
@Serializable
|
inline fun Encoder.encodeStructure(
|
||||||
private data class Point3DSerial(val x: Double, val y: Double, val z: Double)
|
desc: SerialDescriptor,
|
||||||
|
vararg typeParams: KSerializer<*> = emptyArray(),
|
||||||
|
block: CompositeEncoder.() -> Unit
|
||||||
|
) {
|
||||||
|
val encoder = beginStructure(desc, *typeParams)
|
||||||
|
encoder.block()
|
||||||
|
encoder.endStructure(desc)
|
||||||
|
}
|
||||||
|
|
||||||
@Serializer(Point3D::class)
|
@Serializer(Point3D::class)
|
||||||
object Point3DSerializer : KSerializer<Point3D> {
|
object Point3DSerializer : KSerializer<Point3D> {
|
||||||
private val serializer = Point3DSerial.serializer()
|
override val descriptor: SerialDescriptor = descriptor("hep.dataforge.vis.spatial.Point3D") {
|
||||||
override val descriptor: SerialDescriptor get() = serializer.descriptor
|
double("x", true)
|
||||||
|
double("y", true)
|
||||||
|
double("z", true)
|
||||||
|
}
|
||||||
|
|
||||||
override fun deserialize(decoder: Decoder): Point3D {
|
override fun deserialize(decoder: Decoder): Point3D {
|
||||||
return serializer.deserialize(decoder).let {
|
var x: Double? = null
|
||||||
Point3D(it.x, it.y, it.z)
|
var y: Double? = null
|
||||||
|
var z: Double? = null
|
||||||
|
decoder.decodeStructure(descriptor) {
|
||||||
|
loop@ while (true) {
|
||||||
|
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
|
||||||
|
else -> throw SerializationException("Unknown index $i")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return Point3D(x?:0.0, y?:0.0, z?:0.0)
|
||||||
|
}
|
||||||
|
|
||||||
override fun serialize(encoder: Encoder, obj: Point3D) {
|
override fun serialize(encoder: Encoder, obj: Point3D) {
|
||||||
serializer.serialize(encoder, Point3DSerial(obj.x, obj.y, obj.z) )
|
encoder.encodeStructure(descriptor) {
|
||||||
|
if (obj.x != 0.0) encodeDoubleElement(descriptor, 0, obj.x)
|
||||||
|
if (obj.y != 0.0) encodeDoubleElement(descriptor, 1, obj.y)
|
||||||
|
if (obj.z != 0.0) encodeDoubleElement(descriptor, 2, obj.z)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializer(Point2D::class)
|
@Serializer(Point2D::class)
|
||||||
object Point2DSerializer : KSerializer<Point2D> {
|
object Point2DSerializer : KSerializer<Point2D> {
|
||||||
private val serializer = Point2DSerial.serializer()
|
override val descriptor: SerialDescriptor = descriptor("hep.dataforge.vis.spatial.Point2D") {
|
||||||
override val descriptor: SerialDescriptor get() = serializer.descriptor
|
double("x", true)
|
||||||
|
double("y", true)
|
||||||
|
}
|
||||||
|
|
||||||
override fun deserialize(decoder: Decoder): Point2D {
|
override fun deserialize(decoder: Decoder): Point2D {
|
||||||
return serializer.deserialize(decoder).let {
|
var x: Double? = null
|
||||||
Point2D(it.x, it.y)
|
var y: Double? = null
|
||||||
|
decoder.decodeStructure(descriptor) {
|
||||||
|
loop@ while (true) {
|
||||||
|
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
|
||||||
|
else -> throw SerializationException("Unknown index $i")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return Point2D(x?:0.0, y?:0.0)
|
||||||
|
}
|
||||||
|
|
||||||
override fun serialize(encoder: Encoder, obj: Point2D) {
|
override fun serialize(encoder: Encoder, obj: Point2D) {
|
||||||
serializer.serialize(encoder, Point2DSerial(obj.x, obj.y))
|
encoder.encodeStructure(descriptor) {
|
||||||
|
if (obj.x != 0.0) encodeDoubleElement(descriptor, 0, obj.x)
|
||||||
|
if (obj.y != 0.0) encodeDoubleElement(descriptor, 1, obj.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializer(NameToken::class)
|
||||||
|
object NameTokenSerializer : KSerializer<NameToken> {
|
||||||
|
override val descriptor: SerialDescriptor = StringDescriptor.withName("NameToken")
|
||||||
|
|
||||||
|
override fun deserialize(decoder: Decoder): NameToken {
|
||||||
|
return decoder.decodeString().toName().first()!!
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun serialize(encoder: Encoder, obj: NameToken) {
|
||||||
|
encoder.encodeString(obj.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package hep.dataforge.vis.spatial.specifications
|
||||||
|
|
||||||
|
import hep.dataforge.meta.*
|
||||||
|
|
||||||
|
class AxesSpec(override val config: Config) : Specific {
|
||||||
|
var visible by boolean(!config.isEmpty())
|
||||||
|
var size by double(AXIS_SIZE)
|
||||||
|
var width by double(AXIS_WIDTH)
|
||||||
|
|
||||||
|
companion object : Specification<AxesSpec> {
|
||||||
|
override fun wrap(config: Config): AxesSpec = AxesSpec(config)
|
||||||
|
|
||||||
|
const val AXIS_SIZE = 1000.0
|
||||||
|
const val AXIS_WIDTH = 3.0
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
package hep.dataforge.vis.spatial.specifications
|
||||||
|
|
||||||
|
import hep.dataforge.meta.*
|
||||||
|
import kotlin.math.PI
|
||||||
|
|
||||||
|
class CameraSpec(override val config: Config) : Specific {
|
||||||
|
var fov by int(FIELD_OF_VIEW)
|
||||||
|
//var aspect by double(1.0)
|
||||||
|
var nearClip by double(NEAR_CLIP)
|
||||||
|
var farClip by double(FAR_CLIP)
|
||||||
|
|
||||||
|
var distance by double(INITIAL_DISTANCE)
|
||||||
|
var azimuth by double(INITIAL_AZIMUTH)
|
||||||
|
var latitude by double(INITIAL_LATITUDE)
|
||||||
|
val zenith: Double get() = PI / 2 - latitude
|
||||||
|
|
||||||
|
companion object : Specification<CameraSpec> {
|
||||||
|
override fun wrap(config: Config): CameraSpec = CameraSpec(config)
|
||||||
|
const val INITIAL_DISTANCE = 300.0
|
||||||
|
const val INITIAL_AZIMUTH = 0.0
|
||||||
|
const val INITIAL_LATITUDE = PI/6
|
||||||
|
const val NEAR_CLIP = 0.1
|
||||||
|
const val FAR_CLIP = 10000.0
|
||||||
|
const val FIELD_OF_VIEW = 75
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package hep.dataforge.vis.spatial.specifications
|
||||||
|
|
||||||
|
import hep.dataforge.meta.*
|
||||||
|
|
||||||
|
class CanvasSpec(override val config: Config) : Specific {
|
||||||
|
var axes by spec(AxesSpec)
|
||||||
|
var camera by spec(CameraSpec)
|
||||||
|
var controls by spec(ControlsSpec)
|
||||||
|
var minSize by int(300)
|
||||||
|
|
||||||
|
companion object: Specification<CanvasSpec>{
|
||||||
|
override fun wrap(config: Config): CanvasSpec = CanvasSpec(config)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package hep.dataforge.vis.spatial.specifications
|
||||||
|
|
||||||
|
import hep.dataforge.meta.Config
|
||||||
|
import hep.dataforge.meta.Specific
|
||||||
|
import hep.dataforge.meta.Specification
|
||||||
|
|
||||||
|
class ControlsSpec(override val config: Config) : Specific {
|
||||||
|
companion object : Specification<ControlsSpec> {
|
||||||
|
override fun wrap(config: Config): ControlsSpec = ControlsSpec(config)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
package hep.dataforge.vis.spatial.transform
|
||||||
|
|
||||||
|
import hep.dataforge.meta.update
|
||||||
|
import hep.dataforge.names.asName
|
||||||
|
import hep.dataforge.vis.common.MutableVisualGroup
|
||||||
|
import hep.dataforge.vis.common.VisualGroup
|
||||||
|
import hep.dataforge.vis.common.VisualObject
|
||||||
|
import hep.dataforge.vis.spatial.*
|
||||||
|
|
||||||
|
internal fun mergeChild(parent: VisualGroup, child: VisualObject): VisualObject {
|
||||||
|
return child.apply {
|
||||||
|
|
||||||
|
config.update(parent.config)
|
||||||
|
|
||||||
|
//parent.properties?.let { config.update(it) }
|
||||||
|
|
||||||
|
if (this is VisualObject3D && parent is VisualObject3D) {
|
||||||
|
position = (position ?: World.ZERO) + (parent.position ?: World.ZERO)
|
||||||
|
rotation = (parent.rotation ?: World.ZERO) + (parent.rotation ?: World.ZERO)
|
||||||
|
scale = when {
|
||||||
|
scale == null && parent.scale == null -> null
|
||||||
|
scale == null -> parent.scale
|
||||||
|
parent.scale == null -> scale
|
||||||
|
else -> Point3D(
|
||||||
|
scale!!.x * parent.scale!!.x,
|
||||||
|
scale!!.y * parent.scale!!.y,
|
||||||
|
scale!!.z * parent.scale!!.z
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object RemoveSingleChild : VisualTreeTransform<VisualGroup3D>() {
|
||||||
|
|
||||||
|
override fun VisualGroup3D.transformInPlace() {
|
||||||
|
fun MutableVisualGroup.replaceChildren() {
|
||||||
|
children.forEach { (childName, parent) ->
|
||||||
|
if (parent is Proxy) return@forEach //ignore refs
|
||||||
|
if (parent is MutableVisualGroup) {
|
||||||
|
parent.replaceChildren()
|
||||||
|
}
|
||||||
|
if (parent is VisualGroup && parent.children.size == 1) {
|
||||||
|
val child = parent.children.values.first()
|
||||||
|
val newParent = mergeChild(parent, child)
|
||||||
|
newParent.parent = null
|
||||||
|
set(childName.asName(), newParent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
replaceChildren()
|
||||||
|
prototypes?.replaceChildren()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun VisualGroup3D.clone(): VisualGroup3D {
|
||||||
|
TODO()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
package hep.dataforge.vis.spatial.transform
|
||||||
|
|
||||||
|
import hep.dataforge.names.Name
|
||||||
|
import hep.dataforge.names.asName
|
||||||
|
import hep.dataforge.vis.common.MutableVisualGroup
|
||||||
|
import hep.dataforge.vis.common.VisualGroup
|
||||||
|
import hep.dataforge.vis.spatial.Proxy
|
||||||
|
import hep.dataforge.vis.spatial.VisualGroup3D
|
||||||
|
|
||||||
|
object UnRef : VisualTreeTransform<VisualGroup3D>() {
|
||||||
|
private fun VisualGroup.countRefs(): Map<Name, Int> {
|
||||||
|
return children.values.fold(HashMap()) { reducer, obj ->
|
||||||
|
if (obj is VisualGroup) {
|
||||||
|
val counter = obj.countRefs()
|
||||||
|
counter.forEach { (key, value) ->
|
||||||
|
reducer[key] = (reducer[key] ?: 0) + value
|
||||||
|
}
|
||||||
|
} else if (obj is Proxy) {
|
||||||
|
reducer[obj.templateName] = (reducer[obj.templateName] ?: 0) + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return reducer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun MutableVisualGroup.unref(name: Name) {
|
||||||
|
(this as? VisualGroup3D)?.prototypes?.set(name, null)
|
||||||
|
children.filter { (it.value as? Proxy)?.templateName == name }.forEach { (key, value) ->
|
||||||
|
val proxy = value as Proxy
|
||||||
|
val newChild = mergeChild(proxy, proxy.prototype)
|
||||||
|
newChild.parent = null
|
||||||
|
set(key.asName(), newChild) // replace proxy with merged object
|
||||||
|
}
|
||||||
|
|
||||||
|
children.values.filterIsInstance<MutableVisualGroup>().forEach { it.unref(name) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun VisualGroup3D.transformInPlace() {
|
||||||
|
val counts = countRefs()
|
||||||
|
counts.filter { it.value <= 1 }.forEach {
|
||||||
|
this.unref(it.key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun VisualGroup3D.clone(): VisualGroup3D {
|
||||||
|
TODO()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
package hep.dataforge.vis.spatial.transform
|
||||||
|
|
||||||
|
import hep.dataforge.vis.common.VisualObject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A root class for [VisualObject] tree optimization
|
||||||
|
*/
|
||||||
|
abstract class VisualTreeTransform<T : VisualObject> {
|
||||||
|
protected abstract fun T.transformInPlace()
|
||||||
|
protected abstract fun T.clone(): T
|
||||||
|
|
||||||
|
operator fun invoke(source: T, inPlace: Boolean = true): T {
|
||||||
|
val newSource = if (inPlace) {
|
||||||
|
source
|
||||||
|
} else {
|
||||||
|
source.clone()
|
||||||
|
}
|
||||||
|
newSource.transformInPlace()
|
||||||
|
|
||||||
|
return newSource
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T : VisualObject> T.transform(vararg transform: VisualTreeTransform<T>): T {
|
||||||
|
var res = this
|
||||||
|
transform.forEach {
|
||||||
|
res = it(res)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T : VisualObject> T.transformInPlace(vararg transform: VisualTreeTransform<in T>) {
|
||||||
|
transform.forEach { it(this) }
|
||||||
|
}
|
@ -1,13 +1,13 @@
|
|||||||
package hep.dataforge.vis.spatial
|
package hep.dataforge.vis.spatial
|
||||||
|
|
||||||
import hep.dataforge.meta.get
|
import hep.dataforge.io.toMeta
|
||||||
import hep.dataforge.meta.getAll
|
import hep.dataforge.meta.MetaItem
|
||||||
import hep.dataforge.meta.node
|
import hep.dataforge.meta.getIndexed
|
||||||
import hep.dataforge.names.toName
|
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
class ConvexTest {
|
class ConvexTest {
|
||||||
|
@Suppress("UNUSED_VARIABLE")
|
||||||
@Test
|
@Test
|
||||||
fun testConvexBuilder() {
|
fun testConvexBuilder() {
|
||||||
val group = VisualGroup3D().apply {
|
val group = VisualGroup3D().apply {
|
||||||
@ -25,12 +25,11 @@ class ConvexTest {
|
|||||||
|
|
||||||
val convex = group.first() as Convex
|
val convex = group.first() as Convex
|
||||||
|
|
||||||
val meta = convex.toMeta()
|
val json = Visual3DPlugin.json.toJson(Convex.serializer(), convex)
|
||||||
|
val meta = json.toMeta()
|
||||||
|
|
||||||
val pointsNode = convex.toMeta()["points"].node
|
val points = meta.getIndexed("points").values.map { (it as MetaItem.NodeItem<*>).node.point3D()}
|
||||||
|
assertEquals(8, points.count())
|
||||||
assertEquals(8, pointsNode?.items?.count())
|
|
||||||
val points = pointsNode?.getAll("point".toName())
|
|
||||||
|
|
||||||
assertEquals(8, convex.points.size)
|
assertEquals(8, convex.points.size)
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
package hep.dataforge.vis.spatial
|
package hep.dataforge.vis.spatial
|
||||||
|
|
||||||
import hep.dataforge.vis.common.Colors
|
import hep.dataforge.vis.common.Colors
|
||||||
|
import hep.dataforge.vis.common.get
|
||||||
import kotlin.math.PI
|
import kotlin.math.PI
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
class GroupTest {
|
class GroupTest {
|
||||||
@Test
|
@Test
|
||||||
fun testGroupWithComposite(){
|
fun testGroupWithComposite() {
|
||||||
val group = VisualGroup3D().apply{
|
val group = VisualGroup3D().apply {
|
||||||
union {
|
union("union") {
|
||||||
box(100, 100, 100) {
|
box(100, 100, 100) {
|
||||||
z = 100
|
z = 100
|
||||||
rotationX = PI / 4
|
rotationX = PI / 4
|
||||||
@ -17,11 +18,11 @@ class GroupTest {
|
|||||||
}
|
}
|
||||||
box(100, 100, 100)
|
box(100, 100, 100)
|
||||||
material {
|
material {
|
||||||
"color" to Colors.lightgreen
|
color(Colors.lightgreen)
|
||||||
"opacity" to 0.3
|
opacity = 0.3f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
intersect{
|
intersect("intersect") {
|
||||||
box(100, 100, 100) {
|
box(100, 100, 100) {
|
||||||
z = 100
|
z = 100
|
||||||
rotationX = PI / 4
|
rotationX = PI / 4
|
||||||
@ -31,7 +32,7 @@ class GroupTest {
|
|||||||
y = 300
|
y = 300
|
||||||
color(Colors.red)
|
color(Colors.red)
|
||||||
}
|
}
|
||||||
subtract{
|
subtract("subtract") {
|
||||||
box(100, 100, 100) {
|
box(100, 100, 100) {
|
||||||
z = 100
|
z = 100
|
||||||
rotationX = PI / 4
|
rotationX = PI / 4
|
||||||
@ -44,7 +45,7 @@ class GroupTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
assertEquals(3, group.count())
|
assertEquals(3, group.count())
|
||||||
assertEquals(300.0,group.toList()[1].y.toDouble())
|
assertEquals(300.0, (group["intersect"] as VisualObject3D).y.toDouble())
|
||||||
assertEquals(-300.0,group.toList()[2].y.toDouble())
|
assertEquals(-300.0, (group["subtract"] as VisualObject3D).y.toDouble())
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
package hep.dataforge.vis.spatial
|
||||||
|
|
||||||
|
import hep.dataforge.meta.int
|
||||||
|
import hep.dataforge.meta.set
|
||||||
|
import hep.dataforge.names.asName
|
||||||
|
import hep.dataforge.vis.common.useStyle
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class PropertyTest {
|
||||||
|
@Test
|
||||||
|
fun testInheritedProperty() {
|
||||||
|
var box: Box? = null
|
||||||
|
val group = VisualGroup3D().apply {
|
||||||
|
config["test"] = 22
|
||||||
|
group {
|
||||||
|
box = box(100, 100, 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertEquals(22, box?.getProperty("test".asName()).int)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testStyleProperty() {
|
||||||
|
var box: Box? = null
|
||||||
|
val group = VisualGroup3D().apply {
|
||||||
|
styleSheet {
|
||||||
|
set("testStyle") {
|
||||||
|
"test" put 22
|
||||||
|
}
|
||||||
|
}
|
||||||
|
group {
|
||||||
|
box = box(100, 100, 100).apply {
|
||||||
|
useStyle("testStyle")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertEquals(22, box?.getProperty("test".asName()).int)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testColor() {
|
||||||
|
var box: Box? = null
|
||||||
|
val group = VisualGroup3D().apply {
|
||||||
|
styleSheet {
|
||||||
|
set("testStyle") {
|
||||||
|
Material3D.MATERIAL_COLOR_KEY put "#555555"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
group {
|
||||||
|
box = box(100, 100, 100) {
|
||||||
|
useStyle("testStyle")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertEquals("#555555", box?.color)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testProxyStyleProperty() {
|
||||||
|
var box: Proxy? = null
|
||||||
|
val group = VisualGroup3D().apply {
|
||||||
|
styleSheet {
|
||||||
|
set("testStyle") {
|
||||||
|
Material3D.MATERIAL_COLOR_KEY put "#555555"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prototypes {
|
||||||
|
box(100, 100, 100, name = "box") {
|
||||||
|
styles = listOf("testStyle")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
group {
|
||||||
|
box = ref("box".asName())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertEquals("#555555", box?.color)
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
package hep.dataforge.vis.spatial
|
package hep.dataforge.vis.spatial
|
||||||
|
|
||||||
import hep.dataforge.context.Global
|
import hep.dataforge.vis.spatial.Visual3DPlugin.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
|
||||||
@ -8,13 +8,15 @@ import kotlin.test.assertEquals
|
|||||||
class SerializationTest {
|
class SerializationTest {
|
||||||
@ImplicitReflectionSerializer
|
@ImplicitReflectionSerializer
|
||||||
@Test
|
@Test
|
||||||
fun testCubeSerialization(){
|
fun testCubeSerialization() {
|
||||||
val cube = Box(100f,100f,100f).apply{
|
val cube = Box(100f, 100f, 100f).apply {
|
||||||
color(222)
|
color(222)
|
||||||
|
x = 100
|
||||||
|
z = -100
|
||||||
}
|
}
|
||||||
val meta = cube.toMeta()
|
val string = json.stringify(Box.serializer(), cube)
|
||||||
println(meta)
|
println(string)
|
||||||
val newCube = Box(Global,null, meta)
|
val newCube = json.parse(Box.serializer(), string)
|
||||||
assertEquals(cube,newCube)
|
assertEquals(cube.config, newCube.config)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,8 +1,14 @@
|
|||||||
package hep.dataforge.vis.spatial
|
package hep.dataforge.vis.spatial
|
||||||
|
|
||||||
|
|
||||||
import info.laht.threekt.math.Vector2
|
import info.laht.threekt.math.Vector2
|
||||||
import info.laht.threekt.math.Vector3
|
import info.laht.threekt.math.Vector3
|
||||||
|
import info.laht.threekt.math.plus
|
||||||
|
|
||||||
actual typealias Point2D = Vector2
|
actual typealias Point2D = Vector2
|
||||||
|
|
||||||
actual typealias Point3D = Vector3
|
actual typealias Point3D = Vector3
|
||||||
|
|
||||||
|
actual operator fun Point3D.plus(other: Point3D): Point3D {
|
||||||
|
return this.plus(other)
|
||||||
|
}
|
@ -1,63 +0,0 @@
|
|||||||
package hep.dataforge.vis.spatial.three
|
|
||||||
|
|
||||||
import hep.dataforge.meta.*
|
|
||||||
import hep.dataforge.values.ValueType
|
|
||||||
import hep.dataforge.vis.common.Colors
|
|
||||||
import info.laht.threekt.materials.Material
|
|
||||||
import info.laht.threekt.materials.MeshBasicMaterial
|
|
||||||
import info.laht.threekt.materials.MeshPhongMaterial
|
|
||||||
import info.laht.threekt.math.Color
|
|
||||||
|
|
||||||
|
|
||||||
object Materials {
|
|
||||||
val DEFAULT_COLOR = Color(Colors.darkgreen)
|
|
||||||
val DEFAULT = MeshPhongMaterial().apply {
|
|
||||||
this.color.set(DEFAULT_COLOR)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Infer color based on meta item
|
|
||||||
*/
|
|
||||||
fun MetaItem<*>.color(): Color {
|
|
||||||
return when (this) {
|
|
||||||
is MetaItem.ValueItem -> if (this.value.type == ValueType.STRING) {
|
|
||||||
Color(this.value.string)
|
|
||||||
} else {
|
|
||||||
val int = value.number.toInt()
|
|
||||||
// val red = int and 0x00ff0000 shr 16
|
|
||||||
// val green = int and 0x0000ff00 shr 8
|
|
||||||
// val blue = int and 0x000000ff
|
|
||||||
Color(int)
|
|
||||||
}
|
|
||||||
is MetaItem.NodeItem -> {
|
|
||||||
Color(
|
|
||||||
node["red"]?.int ?: 0,
|
|
||||||
node["green"]?.int ?: 0,
|
|
||||||
node["blue"]?.int ?: 0
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val materialCache = HashMap<Meta, Material>()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Infer Three material based on meta item
|
|
||||||
*/
|
|
||||||
fun Meta?.jsMaterial(): Material {
|
|
||||||
return if (this == null) {
|
|
||||||
Materials.DEFAULT
|
|
||||||
} else
|
|
||||||
//TODO add more options for material
|
|
||||||
return materialCache.getOrPut(this) {
|
|
||||||
MeshBasicMaterial().apply {
|
|
||||||
color = get("color")?.color() ?: Materials.DEFAULT_COLOR
|
|
||||||
opacity = get("opacity")?.double ?: 1.0
|
|
||||||
transparent = get("transparent").boolean ?: (opacity < 1.0)
|
|
||||||
//node["specularColor"]?.let { specular = it.color() }
|
|
||||||
side = 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,119 @@
|
|||||||
|
package hep.dataforge.vis.spatial.three
|
||||||
|
|
||||||
|
import hep.dataforge.meta.boolean
|
||||||
|
import hep.dataforge.meta.node
|
||||||
|
import hep.dataforge.names.asName
|
||||||
|
import hep.dataforge.names.plus
|
||||||
|
import hep.dataforge.names.startsWith
|
||||||
|
import hep.dataforge.vis.spatial.Material3D
|
||||||
|
import hep.dataforge.vis.spatial.VisualObject3D
|
||||||
|
import hep.dataforge.vis.spatial.layer
|
||||||
|
import hep.dataforge.vis.spatial.three.ThreeMaterials.getMaterial
|
||||||
|
import info.laht.threekt.core.BufferGeometry
|
||||||
|
import info.laht.threekt.geometries.EdgesGeometry
|
||||||
|
import info.laht.threekt.geometries.WireframeGeometry
|
||||||
|
import info.laht.threekt.objects.LineSegments
|
||||||
|
import info.laht.threekt.objects.Mesh
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic geometry-based factory
|
||||||
|
*/
|
||||||
|
abstract class MeshThreeFactory<in T : VisualObject3D>(
|
||||||
|
override val type: KClass<in T>
|
||||||
|
) : ThreeFactory<T> {
|
||||||
|
/**
|
||||||
|
* Build a geometry for an object
|
||||||
|
*/
|
||||||
|
abstract fun buildGeometry(obj: T): BufferGeometry
|
||||||
|
|
||||||
|
override fun invoke(obj: T): Mesh {
|
||||||
|
val geometry = buildGeometry(obj)
|
||||||
|
|
||||||
|
//JS sometimes tries to pass Geometry as BufferGeometry
|
||||||
|
@Suppress("USELESS_IS_CHECK") if (geometry !is BufferGeometry) error("BufferGeometry expected")
|
||||||
|
|
||||||
|
//val meshMeta: Meta = obj.properties[Material3D.MATERIAL_KEY]?.node ?: Meta.empty
|
||||||
|
|
||||||
|
val mesh = Mesh(geometry, getMaterial(obj)).apply {
|
||||||
|
matrixAutoUpdate = false
|
||||||
|
applyEdges(obj)
|
||||||
|
applyWireFrame(obj)
|
||||||
|
|
||||||
|
//set position for mesh
|
||||||
|
updatePosition(obj)
|
||||||
|
|
||||||
|
layers.enable(obj.layer)
|
||||||
|
children.forEach {
|
||||||
|
it.layers.enable(obj.layer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//add listener to object properties
|
||||||
|
obj.onPropertyChange(this) { name, _, _ ->
|
||||||
|
when {
|
||||||
|
name.startsWith(VisualObject3D.GEOMETRY_KEY) -> {
|
||||||
|
val oldGeometry = mesh.geometry as BufferGeometry
|
||||||
|
val newGeometry = buildGeometry(obj)
|
||||||
|
oldGeometry.attributes = newGeometry.attributes
|
||||||
|
mesh.applyWireFrame(obj)
|
||||||
|
mesh.applyEdges(obj)
|
||||||
|
newGeometry.dispose()
|
||||||
|
}
|
||||||
|
name.startsWith(WIREFRAME_KEY) -> mesh.applyWireFrame(obj)
|
||||||
|
name.startsWith(EDGES_KEY) -> mesh.applyEdges(obj)
|
||||||
|
else -> mesh.updateProperty(obj, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mesh
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val EDGES_KEY = "edges".asName()
|
||||||
|
val WIREFRAME_KEY = "wireframe".asName()
|
||||||
|
val ENABLED_KEY = "enabled".asName()
|
||||||
|
val EDGES_ENABLED_KEY = EDGES_KEY + ENABLED_KEY
|
||||||
|
val EDGES_MATERIAL_KEY = EDGES_KEY + Material3D.MATERIAL_KEY
|
||||||
|
val WIREFRAME_ENABLED_KEY = WIREFRAME_KEY + ENABLED_KEY
|
||||||
|
val WIREFRAME_MATERIAL_KEY = WIREFRAME_KEY + Material3D.MATERIAL_KEY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Mesh.applyEdges(obj: VisualObject3D) {
|
||||||
|
children.find { it.name == "edges" }?.let {
|
||||||
|
remove(it)
|
||||||
|
(it as LineSegments).dispose()
|
||||||
|
}
|
||||||
|
//inherited edges definition, enabled by default
|
||||||
|
if (obj.getProperty(MeshThreeFactory.EDGES_ENABLED_KEY).boolean != false) {
|
||||||
|
|
||||||
|
val material = ThreeMaterials.getLineMaterial(obj.getProperty(MeshThreeFactory.EDGES_MATERIAL_KEY).node)
|
||||||
|
add(
|
||||||
|
LineSegments(
|
||||||
|
EdgesGeometry(geometry as BufferGeometry),
|
||||||
|
material
|
||||||
|
).apply {
|
||||||
|
name = "edges"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Mesh.applyWireFrame(obj: VisualObject3D) {
|
||||||
|
children.find { it.name == "wireframe" }?.let {
|
||||||
|
remove(it)
|
||||||
|
(it as LineSegments).dispose()
|
||||||
|
}
|
||||||
|
//inherited wireframe definition, disabled by default
|
||||||
|
if (obj.getProperty(MeshThreeFactory.WIREFRAME_ENABLED_KEY).boolean == true) {
|
||||||
|
val material = ThreeMaterials.getLineMaterial(obj.getProperty(MeshThreeFactory.WIREFRAME_MATERIAL_KEY).node)
|
||||||
|
add(
|
||||||
|
LineSegments(
|
||||||
|
WireframeGeometry(geometry as BufferGeometry),
|
||||||
|
material
|
||||||
|
).apply {
|
||||||
|
name = "wireframe"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,215 @@
|
|||||||
|
package hep.dataforge.vis.spatial.three
|
||||||
|
|
||||||
|
import hep.dataforge.context.Context
|
||||||
|
import hep.dataforge.meta.Meta
|
||||||
|
import hep.dataforge.meta.get
|
||||||
|
import hep.dataforge.meta.string
|
||||||
|
import hep.dataforge.names.Name
|
||||||
|
import hep.dataforge.names.plus
|
||||||
|
import hep.dataforge.names.toName
|
||||||
|
import hep.dataforge.output.Renderer
|
||||||
|
import hep.dataforge.vis.common.Colors
|
||||||
|
import hep.dataforge.vis.spatial.VisualObject3D
|
||||||
|
import hep.dataforge.vis.spatial.specifications.CameraSpec
|
||||||
|
import hep.dataforge.vis.spatial.specifications.CanvasSpec
|
||||||
|
import hep.dataforge.vis.spatial.specifications.ControlsSpec
|
||||||
|
import hep.dataforge.vis.spatial.three.ThreeMaterials.HIGHLIGHT_MATERIAL
|
||||||
|
import info.laht.threekt.WebGLRenderer
|
||||||
|
import info.laht.threekt.cameras.PerspectiveCamera
|
||||||
|
import info.laht.threekt.core.BufferGeometry
|
||||||
|
import info.laht.threekt.core.Object3D
|
||||||
|
import info.laht.threekt.core.Raycaster
|
||||||
|
import info.laht.threekt.external.controls.OrbitControls
|
||||||
|
import info.laht.threekt.external.controls.TrackballControls
|
||||||
|
import info.laht.threekt.geometries.EdgesGeometry
|
||||||
|
import info.laht.threekt.helpers.AxesHelper
|
||||||
|
import info.laht.threekt.math.Vector2
|
||||||
|
import info.laht.threekt.objects.LineSegments
|
||||||
|
import info.laht.threekt.objects.Mesh
|
||||||
|
import info.laht.threekt.scenes.Scene
|
||||||
|
import org.w3c.dom.HTMLElement
|
||||||
|
import org.w3c.dom.Node
|
||||||
|
import org.w3c.dom.events.MouseEvent
|
||||||
|
import kotlin.browser.window
|
||||||
|
import kotlin.dom.clear
|
||||||
|
import kotlin.math.cos
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.math.sin
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class ThreeCanvas(element: HTMLElement, val three: ThreePlugin, val spec: CanvasSpec) : Renderer<VisualObject3D> {
|
||||||
|
|
||||||
|
override val context: Context get() = three.context
|
||||||
|
|
||||||
|
var content: VisualObject3D? = null
|
||||||
|
private set
|
||||||
|
|
||||||
|
private var root: Object3D? = null
|
||||||
|
|
||||||
|
private val raycaster = Raycaster()
|
||||||
|
private val mousePosition: Vector2 = Vector2()
|
||||||
|
|
||||||
|
var clickListener: ((Name) -> Unit)? = null
|
||||||
|
|
||||||
|
val axes = AxesHelper(spec.axes.size.toInt()).apply {
|
||||||
|
visible = spec.axes.visible
|
||||||
|
}
|
||||||
|
|
||||||
|
val scene: Scene = Scene().apply {
|
||||||
|
add(axes)
|
||||||
|
}
|
||||||
|
|
||||||
|
val camera = buildCamera(spec.camera)
|
||||||
|
|
||||||
|
init {
|
||||||
|
element.clear()
|
||||||
|
|
||||||
|
//Attach listener to track mouse changes
|
||||||
|
element.addEventListener("mousemove", { event ->
|
||||||
|
(event as? MouseEvent)?.run {
|
||||||
|
val rect = element.getBoundingClientRect()
|
||||||
|
mousePosition.x = ((event.clientX - rect.left) / element.clientWidth) * 2 - 1
|
||||||
|
mousePosition.y = -((event.clientY - rect.top) / element.clientHeight) * 2 + 1
|
||||||
|
}
|
||||||
|
}, false)
|
||||||
|
|
||||||
|
element.addEventListener("mousedown", { event ->
|
||||||
|
val mesh = pick()
|
||||||
|
if (mesh != null) {
|
||||||
|
val name = mesh.fullName()
|
||||||
|
clickListener?.invoke(name)
|
||||||
|
}
|
||||||
|
}, false)
|
||||||
|
|
||||||
|
camera.aspect = 1.0
|
||||||
|
|
||||||
|
val renderer = WebGLRenderer { antialias = true }.apply {
|
||||||
|
setClearColor(Colors.skyblue, 1)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
addControls(renderer.domElement, spec.controls)
|
||||||
|
|
||||||
|
fun animate() {
|
||||||
|
val mesh = pick()
|
||||||
|
|
||||||
|
if (mesh != null && highlighted != mesh) {
|
||||||
|
highlighted?.toggleHighlight(false)
|
||||||
|
mesh.toggleHighlight(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
window.requestAnimationFrame {
|
||||||
|
animate()
|
||||||
|
}
|
||||||
|
renderer.render(scene, camera)
|
||||||
|
}
|
||||||
|
|
||||||
|
element.appendChild(renderer.domElement)
|
||||||
|
|
||||||
|
renderer.setSize(max(spec.minSize, element.offsetWidth), max(spec.minSize, element.offsetWidth))
|
||||||
|
|
||||||
|
element.onresize = {
|
||||||
|
renderer.setSize(element.offsetWidth, element.offsetWidth)
|
||||||
|
camera.updateProjectionMatrix()
|
||||||
|
}
|
||||||
|
|
||||||
|
animate()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun pick(): Mesh? {
|
||||||
|
// update the picking ray with the camera and mouse position
|
||||||
|
raycaster.setFromCamera(mousePosition, camera)
|
||||||
|
|
||||||
|
// calculate objects intersecting the picking ray
|
||||||
|
return root?.let { root ->
|
||||||
|
val intersects = raycaster.intersectObject(root, true)
|
||||||
|
val intersect = intersects.firstOrNull()
|
||||||
|
intersect?.`object` as? Mesh
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve full name of the object relative to the global root
|
||||||
|
*/
|
||||||
|
private fun Object3D.fullName(): Name {
|
||||||
|
if (root == null) error("Can't resolve element name without the root")
|
||||||
|
return if (parent == root) {
|
||||||
|
name.toName()
|
||||||
|
} else {
|
||||||
|
(parent?.fullName() ?: Name.EMPTY) + name.toName()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildCamera(spec: CameraSpec) = PerspectiveCamera(
|
||||||
|
spec.fov,
|
||||||
|
1.0,
|
||||||
|
spec.nearClip,
|
||||||
|
spec.farClip
|
||||||
|
).apply {
|
||||||
|
translateX(spec.distance * sin(spec.zenith) * sin(spec.azimuth))
|
||||||
|
translateY(spec.distance * cos(spec.zenith))
|
||||||
|
translateZ(spec.distance * sin(spec.zenith) * cos(spec.azimuth))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addControls(element: Node, controlsSpec: ControlsSpec) {
|
||||||
|
when (controlsSpec["type"].string) {
|
||||||
|
"trackball" -> TrackballControls(camera, element)
|
||||||
|
else -> OrbitControls(camera, element)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun render(obj: VisualObject3D, meta: Meta) {
|
||||||
|
//clear old root
|
||||||
|
scene.children.find { it.name == "@root" }?.let {
|
||||||
|
scene.remove(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
val object3D = three.buildObject3D(obj)
|
||||||
|
object3D.name = "@root"
|
||||||
|
scene.add(object3D)
|
||||||
|
content = obj
|
||||||
|
root = object3D
|
||||||
|
}
|
||||||
|
|
||||||
|
private var highlighted: Mesh? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle highlight for the given [Mesh] object
|
||||||
|
*/
|
||||||
|
private fun Mesh.toggleHighlight(highlight: Boolean) {
|
||||||
|
if (highlight) {
|
||||||
|
val edges = LineSegments(
|
||||||
|
EdgesGeometry(geometry as BufferGeometry),
|
||||||
|
HIGHLIGHT_MATERIAL
|
||||||
|
).apply {
|
||||||
|
name = "@highlight"
|
||||||
|
}
|
||||||
|
add(edges)
|
||||||
|
highlighted = this
|
||||||
|
} else {
|
||||||
|
val highlightEdges = children.find { it.name == "@highlight" }
|
||||||
|
highlightEdges?.let { remove(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle highlight for element with given name
|
||||||
|
*/
|
||||||
|
fun highlight(name: Name?) {
|
||||||
|
if (name == null) {
|
||||||
|
highlighted?.toggleHighlight(false)
|
||||||
|
highlighted = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val mesh = root?.findChild(name) as? Mesh
|
||||||
|
if (mesh != null && highlighted != mesh) {
|
||||||
|
highlighted?.toggleHighlight(false)
|
||||||
|
mesh.toggleHighlight(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ThreePlugin.output(element: HTMLElement, spec: CanvasSpec = CanvasSpec.empty()): ThreeCanvas =
|
||||||
|
ThreeCanvas(element, this, spec)
|
@ -0,0 +1,56 @@
|
|||||||
|
package hep.dataforge.vis.spatial.three
|
||||||
|
|
||||||
|
import hep.dataforge.vis.spatial.Label3D
|
||||||
|
import hep.dataforge.vis.spatial.color
|
||||||
|
import info.laht.threekt.DoubleSide
|
||||||
|
import info.laht.threekt.core.Object3D
|
||||||
|
import info.laht.threekt.geometries.PlaneBufferGeometry
|
||||||
|
import info.laht.threekt.materials.MeshBasicMaterial
|
||||||
|
import info.laht.threekt.objects.Mesh
|
||||||
|
import info.laht.threekt.textures.Texture
|
||||||
|
import org.w3c.dom.CanvasRenderingContext2D
|
||||||
|
import org.w3c.dom.CanvasTextBaseline
|
||||||
|
import org.w3c.dom.HTMLCanvasElement
|
||||||
|
import org.w3c.dom.MIDDLE
|
||||||
|
import kotlin.browser.document
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Using example from http://stemkoski.github.io/Three.js/Texture-From-Canvas.html
|
||||||
|
*/
|
||||||
|
object ThreeCanvasLabelFactory: ThreeFactory<Label3D> {
|
||||||
|
override val type: KClass<in Label3D> get() = Label3D::class
|
||||||
|
|
||||||
|
override fun invoke(obj: Label3D): Object3D {
|
||||||
|
val canvas = document.createElement("canvas") as HTMLCanvasElement
|
||||||
|
val context = canvas.getContext("2d") as CanvasRenderingContext2D
|
||||||
|
context.font = "Bold ${obj.fontSize}pt ${obj.fontFamily}"
|
||||||
|
context.fillStyle = obj.color ?: "black"
|
||||||
|
context.textBaseline = CanvasTextBaseline.MIDDLE
|
||||||
|
val metrics = context.measureText(obj.text)
|
||||||
|
//canvas.width = metrics.width.toInt()
|
||||||
|
|
||||||
|
|
||||||
|
context.fillText(obj.text, (canvas.width - metrics.width)/2, 0.5*canvas.height)
|
||||||
|
|
||||||
|
|
||||||
|
// canvas contents will be used for a texture
|
||||||
|
val texture = Texture(canvas)
|
||||||
|
texture.needsUpdate = true
|
||||||
|
|
||||||
|
val material = MeshBasicMaterial().apply {
|
||||||
|
map = texture
|
||||||
|
side = DoubleSide
|
||||||
|
transparent = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val mesh = Mesh(
|
||||||
|
PlaneBufferGeometry(canvas.width, canvas.height),
|
||||||
|
material
|
||||||
|
)
|
||||||
|
|
||||||
|
mesh.updatePosition(obj)
|
||||||
|
|
||||||
|
return mesh
|
||||||
|
}
|
||||||
|
}
|
@ -6,7 +6,7 @@ import info.laht.threekt.core.BufferGeometry
|
|||||||
import info.laht.threekt.objects.Mesh
|
import info.laht.threekt.objects.Mesh
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This should be inner, becaulse it uses object builder
|
* This should be inner, because it uses object builder
|
||||||
*/
|
*/
|
||||||
class ThreeCompositeFactory(val three: ThreePlugin) : MeshThreeFactory<Composite>(Composite::class) {
|
class ThreeCompositeFactory(val three: ThreePlugin) : MeshThreeFactory<Composite>(Composite::class) {
|
||||||
|
|
||||||
|
@ -1,19 +1,15 @@
|
|||||||
package hep.dataforge.vis.spatial.three
|
package hep.dataforge.vis.spatial.three
|
||||||
|
|
||||||
import hep.dataforge.meta.boolean
|
import hep.dataforge.names.Name
|
||||||
import hep.dataforge.meta.int
|
|
||||||
import hep.dataforge.meta.node
|
|
||||||
import hep.dataforge.names.plus
|
|
||||||
import hep.dataforge.names.startsWith
|
import hep.dataforge.names.startsWith
|
||||||
import hep.dataforge.provider.Type
|
import hep.dataforge.provider.Type
|
||||||
import hep.dataforge.vis.common.asName
|
import hep.dataforge.vis.common.VisualObject
|
||||||
import hep.dataforge.vis.spatial.*
|
import hep.dataforge.vis.spatial.*
|
||||||
|
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_KEY
|
||||||
import hep.dataforge.vis.spatial.three.ThreeFactory.Companion.TYPE
|
import hep.dataforge.vis.spatial.three.ThreeFactory.Companion.TYPE
|
||||||
|
import hep.dataforge.vis.spatial.three.ThreeMaterials.getMaterial
|
||||||
import info.laht.threekt.core.BufferGeometry
|
import info.laht.threekt.core.BufferGeometry
|
||||||
import info.laht.threekt.core.Object3D
|
import info.laht.threekt.core.Object3D
|
||||||
import info.laht.threekt.geometries.EdgesGeometry
|
|
||||||
import info.laht.threekt.geometries.WireframeGeometry
|
|
||||||
import info.laht.threekt.objects.LineSegments
|
|
||||||
import info.laht.threekt.objects.Mesh
|
import info.laht.threekt.objects.Mesh
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
@ -21,9 +17,9 @@ import kotlin.reflect.KClass
|
|||||||
* Builder and updater for three.js object
|
* Builder and updater for three.js object
|
||||||
*/
|
*/
|
||||||
@Type(TYPE)
|
@Type(TYPE)
|
||||||
interface ThreeFactory<T : VisualObject3D> {
|
interface ThreeFactory<in T : VisualObject> {
|
||||||
|
|
||||||
val type: KClass<out T>
|
val type: KClass<in T>
|
||||||
|
|
||||||
operator fun invoke(obj: T): Object3D
|
operator fun invoke(obj: T): Object3D
|
||||||
|
|
||||||
@ -35,112 +31,41 @@ interface ThreeFactory<T : VisualObject3D> {
|
|||||||
/**
|
/**
|
||||||
* Update position, rotation and visibility
|
* Update position, rotation and visibility
|
||||||
*/
|
*/
|
||||||
internal fun Object3D.updatePosition(obj: VisualObject3D) {
|
fun Object3D.updatePosition(obj: VisualObject3D) {
|
||||||
visible = obj.visible ?: true
|
visible = obj.visible ?: true
|
||||||
position.set(obj.x, obj.y, obj.z)
|
position.set(obj.x, obj.y, obj.z)
|
||||||
// obj.rotation?.let{
|
|
||||||
// rotateZ(it.z)
|
|
||||||
// rotateX(it.x)
|
|
||||||
// rotateY(it.y)
|
|
||||||
// }
|
|
||||||
setRotationFromEuler(obj.euler)
|
setRotationFromEuler(obj.euler)
|
||||||
scale.set(obj.scaleX, obj.scaleY, obj.scaleZ)
|
scale.set(obj.scaleX, obj.scaleY, obj.scaleZ)
|
||||||
updateMatrix()
|
updateMatrix()
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun <T : VisualObject3D> Mesh.updateFrom(obj: T) {
|
///**
|
||||||
matrixAutoUpdate = false
|
// * Unsafe invocation of a factory
|
||||||
|
// */
|
||||||
//inherited edges definition, enabled by default
|
//operator fun <T : VisualObject3D> ThreeFactory<T>.invoke(obj: Any): Object3D {
|
||||||
if (obj.getProperty(MeshThreeFactory.EDGES_ENABLED_KEY).boolean != false) {
|
// if (type.isInstance(obj)) {
|
||||||
val material = obj.getProperty(MeshThreeFactory.EDGES_MATERIAL_KEY).node?.jsMaterial() ?: Materials.DEFAULT
|
// @Suppress("UNCHECKED_CAST")
|
||||||
add(LineSegments(EdgesGeometry(geometry as BufferGeometry), material))
|
// return invoke(obj as T)
|
||||||
}
|
// } else {
|
||||||
|
// error("The object of type ${obj::class} could not be rendered by this factory")
|
||||||
//inherited wireframe definition, disabled by default
|
// }
|
||||||
if (obj.getProperty(MeshThreeFactory.WIREFRAME_ENABLED_KEY).boolean == true) {
|
//}
|
||||||
val material = obj.getProperty(MeshThreeFactory.WIREFRAME_MATERIAL_KEY).node?.jsMaterial() ?: Materials.DEFAULT
|
|
||||||
add(LineSegments(WireframeGeometry(geometry as BufferGeometry), material))
|
|
||||||
}
|
|
||||||
|
|
||||||
//set position for mesh
|
|
||||||
updatePosition(obj)
|
|
||||||
|
|
||||||
obj.getProperty(MeshThreeFactory.LAYER_KEY).int?.let {
|
|
||||||
layers.set(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unsafe invocation of a factory
|
* Update non-position non-geometry property
|
||||||
*/
|
*/
|
||||||
operator fun <T : VisualObject3D> ThreeFactory<T>.invoke(obj: Any): Object3D {
|
fun Object3D.updateProperty(source: VisualObject3D, propertyName: Name) {
|
||||||
if (type.isInstance(obj)) {
|
if (this is Mesh && propertyName.startsWith(MATERIAL_KEY)) {
|
||||||
@Suppress("UNCHECKED_CAST")
|
this.material = getMaterial(source)
|
||||||
return invoke(obj as T)
|
|
||||||
} else {
|
|
||||||
error("The object of type ${obj::class} could not be rendered by this factory")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Basic geometry-based factory
|
|
||||||
*/
|
|
||||||
abstract class MeshThreeFactory<T : VisualObject3D>(override val type: KClass<out T>) : ThreeFactory<T> {
|
|
||||||
/**
|
|
||||||
* Build a geometry for an object
|
|
||||||
*/
|
|
||||||
abstract fun buildGeometry(obj: T): BufferGeometry
|
|
||||||
|
|
||||||
|
|
||||||
override fun invoke(obj: T): Mesh {
|
|
||||||
//create mesh from geometry
|
|
||||||
return buildMesh<T>(obj) { buildGeometry(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
val EDGES_KEY = "edges".asName()
|
|
||||||
val WIREFRAME_KEY = "wireframe".asName()
|
|
||||||
val ENABLED_KEY = "enabled".asName()
|
|
||||||
val EDGES_ENABLED_KEY = EDGES_KEY + ENABLED_KEY
|
|
||||||
val EDGES_MATERIAL_KEY = EDGES_KEY + VisualObject3D.MATERIAL_KEY
|
|
||||||
val WIREFRAME_ENABLED_KEY = WIREFRAME_KEY + ENABLED_KEY
|
|
||||||
val WIREFRAME_MATERIAL_KEY = WIREFRAME_KEY + VisualObject3D.MATERIAL_KEY
|
|
||||||
val LAYER_KEY = "layer".asName()
|
|
||||||
|
|
||||||
fun <T : VisualObject3D> buildMesh(obj: T, geometryBuilder: (T) -> BufferGeometry): Mesh {
|
|
||||||
//TODO add caching for geometries using templates
|
|
||||||
val geometry = geometryBuilder(obj)
|
|
||||||
|
|
||||||
//JS sometimes tries to pass Geometry as BufferGeometry
|
|
||||||
@Suppress("USELESS_IS_CHECK") if (geometry !is BufferGeometry) error("BufferGeometry expected")
|
|
||||||
|
|
||||||
val mesh = Mesh(geometry, obj.material.jsMaterial())
|
|
||||||
|
|
||||||
mesh.updateFrom(obj)
|
|
||||||
|
|
||||||
//add listener to object properties
|
|
||||||
obj.onPropertyChange(this) { name, _, _ ->
|
|
||||||
if (name.startsWith(VisualObject3D.MATERIAL_KEY)) {
|
|
||||||
//updated material
|
|
||||||
mesh.material = obj.material.jsMaterial()
|
|
||||||
} else if (
|
} else if (
|
||||||
name.startsWith(VisualObject3D.position) ||
|
propertyName.startsWith(VisualObject3D.position)
|
||||||
name.startsWith(VisualObject3D.rotation) ||
|
|| propertyName.startsWith(VisualObject3D.rotation)
|
||||||
name.startsWith(VisualObject3D.scale)
|
|| propertyName.startsWith(VisualObject3D.scale)
|
||||||
) {
|
) {
|
||||||
//update position of mesh using this object
|
//update position of mesh using this object
|
||||||
mesh.updatePosition(obj)
|
updatePosition(source)
|
||||||
} else if (name == VisualObject3D.VISIBLE_KEY) {
|
} else if (propertyName == VisualObject3D.VISIBLE_KEY) {
|
||||||
mesh.visible = obj.visible ?: true
|
visible = source.visible ?: true
|
||||||
} else {
|
|
||||||
//full update
|
|
||||||
mesh.geometry = geometryBuilder(obj)
|
|
||||||
mesh.material = obj.material.jsMaterial()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mesh
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,9 @@ import info.laht.threekt.core.Face3
|
|||||||
import info.laht.threekt.core.Geometry
|
import info.laht.threekt.core.Geometry
|
||||||
import info.laht.threekt.math.Vector3
|
import info.laht.threekt.math.Vector3
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An implementation of geometry builder for Three.js [BufferGeometry]
|
||||||
|
*/
|
||||||
class ThreeGeometryBuilder : GeometryBuilder<BufferGeometry> {
|
class ThreeGeometryBuilder : GeometryBuilder<BufferGeometry> {
|
||||||
|
|
||||||
private val vertices = ArrayList<Point3D>()
|
private val vertices = ArrayList<Point3D>()
|
||||||
@ -31,7 +34,7 @@ class ThreeGeometryBuilder : GeometryBuilder<BufferGeometry> {
|
|||||||
override fun face(vertex1: Point3D, vertex2: Point3D, vertex3: Point3D, normal: Point3D?, meta: Meta) {
|
override fun face(vertex1: Point3D, vertex2: Point3D, vertex3: Point3D, normal: Point3D?, meta: Meta) {
|
||||||
val face = Face3(append(vertex1), append(vertex2), append(vertex3), normal ?: Vector3(0, 0, 0))
|
val face = Face3(append(vertex1), append(vertex2), append(vertex3), normal ?: Vector3(0, 0, 0))
|
||||||
meta["materialIndex"].int?.let { face.materialIndex = it }
|
meta["materialIndex"].int?.let { face.materialIndex = it }
|
||||||
meta["color"]?.color()?.let { face.color = it }
|
meta["color"]?.getColor()?.let { face.color = it }
|
||||||
faces.add(face)
|
faces.add(face)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
package hep.dataforge.vis.spatial.three
|
||||||
|
|
||||||
|
import hep.dataforge.js.jsObject
|
||||||
|
import hep.dataforge.meta.MetaItem
|
||||||
|
import hep.dataforge.names.Name
|
||||||
|
import hep.dataforge.vis.spatial.Label3D
|
||||||
|
import hep.dataforge.vis.spatial.three.ThreeMaterials.getMaterial
|
||||||
|
import info.laht.threekt.core.Object3D
|
||||||
|
import info.laht.threekt.geometries.TextBufferGeometry
|
||||||
|
import info.laht.threekt.objects.Mesh
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
object ThreeLabelFactory : ThreeFactory<Label3D> {
|
||||||
|
override val type: KClass<in Label3D> get() = Label3D::class
|
||||||
|
|
||||||
|
override fun invoke(obj: Label3D): Object3D {
|
||||||
|
val textGeo = TextBufferGeometry( obj.text, jsObject {
|
||||||
|
font = obj.fontFamily
|
||||||
|
size = 20
|
||||||
|
height = 1
|
||||||
|
curveSegments = 1
|
||||||
|
} )
|
||||||
|
return Mesh(textGeo, getMaterial(obj)).apply {
|
||||||
|
updatePosition(obj)
|
||||||
|
obj.onPropertyChange(this@ThreeLabelFactory){name: Name, before: MetaItem<*>?, after: MetaItem<*>? ->
|
||||||
|
//TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
package hep.dataforge.vis.spatial.three
|
||||||
|
|
||||||
|
import hep.dataforge.meta.node
|
||||||
|
import hep.dataforge.vis.spatial.PolyLine
|
||||||
|
import hep.dataforge.vis.spatial.color
|
||||||
|
import hep.dataforge.vis.spatial.three.ThreeMaterials.DEFAULT_LINE_COLOR
|
||||||
|
import info.laht.threekt.core.Geometry
|
||||||
|
import info.laht.threekt.core.Object3D
|
||||||
|
import info.laht.threekt.math.Color
|
||||||
|
import info.laht.threekt.objects.LineSegments
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
object ThreeLineFactory : ThreeFactory<PolyLine> {
|
||||||
|
override val type: KClass<PolyLine> get() = PolyLine::class
|
||||||
|
|
||||||
|
override fun invoke(obj: PolyLine): Object3D {
|
||||||
|
val geometry = Geometry().apply {
|
||||||
|
vertices = obj.points.toTypedArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
return LineSegments(geometry, material).apply {
|
||||||
|
updatePosition(obj)
|
||||||
|
//layers.enable(obj.layer)
|
||||||
|
//add listener to object properties
|
||||||
|
obj.onPropertyChange(this) { propertyName, _, _ ->
|
||||||
|
updateProperty(obj, propertyName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,84 @@
|
|||||||
|
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.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
|
||||||
|
import info.laht.threekt.materials.MeshPhongMaterial
|
||||||
|
import info.laht.threekt.math.Color
|
||||||
|
|
||||||
|
|
||||||
|
object ThreeMaterials {
|
||||||
|
val DEFAULT_COLOR = Color(Colors.darkgreen)
|
||||||
|
val DEFAULT = MeshBasicMaterial().apply {
|
||||||
|
color.set(DEFAULT_COLOR)
|
||||||
|
}
|
||||||
|
val DEFAULT_LINE_COLOR = Color(Colors.black)
|
||||||
|
val DEFAULT_LINE = LineBasicMaterial().apply {
|
||||||
|
color.set(DEFAULT_LINE_COLOR)
|
||||||
|
}
|
||||||
|
|
||||||
|
val HIGHLIGHT_MATERIAL = LineBasicMaterial().apply {
|
||||||
|
color.set(Colors.ivory)
|
||||||
|
linewidth = 8.0
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getLineMaterial(meta: Meta?): LineBasicMaterial {
|
||||||
|
if (meta == null) return DEFAULT_LINE
|
||||||
|
return LineBasicMaterial().apply {
|
||||||
|
color = meta[Material3D.COLOR_KEY]?.getColor() ?: DEFAULT_LINE_COLOR
|
||||||
|
opacity = meta[Material3D.OPACITY_KEY].double ?: 1.0
|
||||||
|
transparent = opacity < 1.0
|
||||||
|
linewidth = meta["thickness"].double ?: 1.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getMaterial(visualObject3D: VisualObject3D): Material {
|
||||||
|
val meta = visualObject3D.getProperty(Material3D.MATERIAL_KEY).node ?: return ThreeMaterials.DEFAULT
|
||||||
|
return if (meta[Material3D.SPECULAR_COLOR] != null) {
|
||||||
|
MeshPhongMaterial().apply {
|
||||||
|
color = meta[Material3D.COLOR_KEY]?.getColor() ?: DEFAULT_COLOR
|
||||||
|
specular = meta[Material3D.SPECULAR_COLOR]!!.getColor()
|
||||||
|
opacity = meta[Material3D.OPACITY_KEY]?.double ?: 1.0
|
||||||
|
transparent = opacity < 1.0
|
||||||
|
wireframe = meta[Material3D.WIREFRAME_KEY].boolean ?: false
|
||||||
|
needsUpdate = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
MeshBasicMaterial().apply {
|
||||||
|
color = meta[Material3D.COLOR_KEY]?.getColor() ?: DEFAULT_COLOR
|
||||||
|
opacity = meta[Material3D.OPACITY_KEY]?.double ?: 1.0
|
||||||
|
transparent = opacity < 1.0
|
||||||
|
wireframe = meta[Material3D.WIREFRAME_KEY].boolean ?: false
|
||||||
|
needsUpdate = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Infer color based on meta item
|
||||||
|
*/
|
||||||
|
fun MetaItem<*>.getColor(): Color {
|
||||||
|
return when (this) {
|
||||||
|
is MetaItem.ValueItem -> if (this.value.type == ValueType.NUMBER) {
|
||||||
|
val int = value.number.toInt()
|
||||||
|
Color(int)
|
||||||
|
} else {
|
||||||
|
Color(this.value.string)
|
||||||
|
}
|
||||||
|
is MetaItem.NodeItem -> {
|
||||||
|
Color(
|
||||||
|
node[Colors.RED_KEY]?.int ?: 0,
|
||||||
|
node[Colors.GREEN_KEY]?.int ?: 0,
|
||||||
|
node[Colors.BLUE_KEY]?.int ?: 0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,71 +0,0 @@
|
|||||||
package hep.dataforge.vis.spatial.three
|
|
||||||
|
|
||||||
import hep.dataforge.context.Context
|
|
||||||
import hep.dataforge.meta.*
|
|
||||||
import hep.dataforge.output.Output
|
|
||||||
import hep.dataforge.vis.common.Colors
|
|
||||||
import hep.dataforge.vis.hmr.require
|
|
||||||
import hep.dataforge.vis.spatial.VisualObject3D
|
|
||||||
import info.laht.threekt.WebGLRenderer
|
|
||||||
import info.laht.threekt.helpers.AxesHelper
|
|
||||||
import info.laht.threekt.lights.AmbientLight
|
|
||||||
import info.laht.threekt.scenes.Scene
|
|
||||||
import org.w3c.dom.Element
|
|
||||||
import kotlin.browser.window
|
|
||||||
|
|
||||||
private val elementResizeEvent = require("element-resize-event")
|
|
||||||
|
|
||||||
class ThreeOutput(val three: ThreePlugin, val meta: Meta = EmptyMeta) : Output<VisualObject3D> {
|
|
||||||
|
|
||||||
override val context: Context get() = three.context
|
|
||||||
|
|
||||||
val scene: Scene = Scene().apply {
|
|
||||||
add(AmbientLight())
|
|
||||||
meta["axis"]?.let {
|
|
||||||
val axesHelper = AxesHelper(it.node["size"].int ?: 1)
|
|
||||||
add(axesHelper)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val camera = three.buildCamera(meta["camera"].node ?: EmptyMeta)
|
|
||||||
|
|
||||||
fun attach(element: Element, computeWidth: Element.() -> Int = { element.clientWidth }) {
|
|
||||||
val width by meta.number(computeWidth(element)).int
|
|
||||||
|
|
||||||
val height: Int = (width / camera.aspect).toInt()
|
|
||||||
|
|
||||||
val renderer = WebGLRenderer { antialias = true }.apply {
|
|
||||||
setClearColor(Colors.skyblue, 1)
|
|
||||||
setSize(width, height)
|
|
||||||
}
|
|
||||||
|
|
||||||
three.addControls(camera, renderer.domElement, meta["controls"].node ?: EmptyMeta)
|
|
||||||
|
|
||||||
fun animate() {
|
|
||||||
window.requestAnimationFrame {
|
|
||||||
animate()
|
|
||||||
}
|
|
||||||
renderer.render(scene, camera)
|
|
||||||
}
|
|
||||||
|
|
||||||
elementResizeEvent(element) {
|
|
||||||
camera.updateProjectionMatrix()
|
|
||||||
val newWidth = computeWidth(element)
|
|
||||||
renderer.setSize(newWidth, (newWidth / camera.aspect).toInt())
|
|
||||||
}
|
|
||||||
|
|
||||||
element.replaceWith(renderer.domElement)
|
|
||||||
animate()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun render(obj: VisualObject3D, meta: Meta) {
|
|
||||||
scene.add(three.buildObject3D(obj))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun ThreePlugin.output(element: Element? = null, meta: Meta = EmptyMeta, override: MetaBuilder.() -> Unit = {}) =
|
|
||||||
ThreeOutput(this, buildMeta(meta, override)).apply {
|
|
||||||
if(element!=null){
|
|
||||||
attach(element)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +1,14 @@
|
|||||||
package hep.dataforge.vis.spatial.three
|
package hep.dataforge.vis.spatial.three
|
||||||
|
|
||||||
import hep.dataforge.context.AbstractPlugin
|
import hep.dataforge.context.*
|
||||||
import hep.dataforge.context.PluginFactory
|
import hep.dataforge.meta.Meta
|
||||||
import hep.dataforge.context.PluginTag
|
import hep.dataforge.names.*
|
||||||
import hep.dataforge.context.content
|
import hep.dataforge.vis.common.VisualObject
|
||||||
import hep.dataforge.meta.*
|
|
||||||
import hep.dataforge.vis.spatial.*
|
import hep.dataforge.vis.spatial.*
|
||||||
import info.laht.threekt.cameras.Camera
|
|
||||||
import info.laht.threekt.cameras.PerspectiveCamera
|
|
||||||
import info.laht.threekt.core.Object3D
|
import info.laht.threekt.core.Object3D
|
||||||
import info.laht.threekt.external.controls.OrbitControls
|
|
||||||
import info.laht.threekt.external.controls.TrackballControls
|
|
||||||
import org.w3c.dom.Node
|
|
||||||
import kotlin.collections.set
|
import kotlin.collections.set
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
import info.laht.threekt.objects.Group as ThreeGroup
|
||||||
|
|
||||||
class ThreePlugin : AbstractPlugin() {
|
class ThreePlugin : AbstractPlugin() {
|
||||||
override val tag: PluginTag get() = Companion.tag
|
override val tag: PluginTag get() = Companion.tag
|
||||||
@ -28,31 +23,81 @@ class ThreePlugin : AbstractPlugin() {
|
|||||||
objectFactories[Convex::class] = ThreeConvexFactory
|
objectFactories[Convex::class] = ThreeConvexFactory
|
||||||
objectFactories[Sphere::class] = ThreeSphereFactory
|
objectFactories[Sphere::class] = ThreeSphereFactory
|
||||||
objectFactories[ConeSegment::class] = ThreeCylinderFactory
|
objectFactories[ConeSegment::class] = ThreeCylinderFactory
|
||||||
|
objectFactories[PolyLine::class] = ThreeLineFactory
|
||||||
|
objectFactories[Label3D::class] = ThreeCanvasLabelFactory
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findObjectFactory(type: KClass<out VisualObject3D>): ThreeFactory<*>? {
|
@Suppress("UNCHECKED_CAST")
|
||||||
return objectFactories[type]
|
private fun findObjectFactory(type: KClass<out VisualObject>): ThreeFactory<VisualObject3D>? {
|
||||||
?: context.content<ThreeFactory<*>>(ThreeFactory.TYPE).values.find { it.type == type }
|
return (objectFactories[type]
|
||||||
|
?: context.content<ThreeFactory<*>>(ThreeFactory.TYPE).values.find { it.type == type })
|
||||||
|
as ThreeFactory<VisualObject3D>?
|
||||||
}
|
}
|
||||||
|
|
||||||
fun buildObject3D(obj: VisualObject3D): Object3D {
|
fun buildObject3D(obj: VisualObject3D): Object3D {
|
||||||
return when (obj) {
|
return when (obj) {
|
||||||
is VisualGroup3D -> Group(obj.mapNotNull {
|
is ThreeVisualObject -> obj.toObject3D()
|
||||||
|
is Proxy -> proxyFactory(obj)
|
||||||
|
is VisualGroup3D -> {
|
||||||
|
val group = ThreeGroup()
|
||||||
|
obj.children.forEach { (token, child) ->
|
||||||
|
if (child is VisualObject3D && child.ignore != true) {
|
||||||
try {
|
try {
|
||||||
buildObject3D(it)
|
val object3D = buildObject3D(child)
|
||||||
|
group[token] = object3D
|
||||||
} catch (ex: Throwable) {
|
} catch (ex: Throwable) {
|
||||||
console.error(ex)
|
logger.error(ex) { "Failed to render $child" }
|
||||||
logger.error(ex) { "Failed to render $it" }
|
|
||||||
null
|
|
||||||
}
|
}
|
||||||
}).apply {
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
group.apply {
|
||||||
updatePosition(obj)
|
updatePosition(obj)
|
||||||
|
//obj.onChildrenChange()
|
||||||
|
|
||||||
|
obj.onPropertyChange(this) { name, _, _ ->
|
||||||
|
if (
|
||||||
|
name.startsWith(VisualObject3D.position) ||
|
||||||
|
name.startsWith(VisualObject3D.rotation) ||
|
||||||
|
name.startsWith(VisualObject3D.scale)
|
||||||
|
) {
|
||||||
|
//update position of mesh using this object
|
||||||
|
updatePosition(obj)
|
||||||
|
} else if (name == VisualObject3D.VISIBLE_KEY) {
|
||||||
|
visible = obj.visible ?: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.onChildrenChange(this) { name, child ->
|
||||||
|
if (name.isEmpty()) {
|
||||||
|
logger.error { "Children change with empty namr on $group" }
|
||||||
|
return@onChildrenChange
|
||||||
|
}
|
||||||
|
|
||||||
|
val parentName = name.cutLast()
|
||||||
|
val childName = name.last()!!
|
||||||
|
|
||||||
|
//removing old object
|
||||||
|
findChild(name)?.let { oldChild ->
|
||||||
|
oldChild.parent?.remove(oldChild)
|
||||||
|
}
|
||||||
|
|
||||||
|
//adding new object
|
||||||
|
if (child != null && child is VisualObject3D) {
|
||||||
|
try {
|
||||||
|
val object3D = buildObject3D(child)
|
||||||
|
set(name, object3D)
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
logger.error(ex) { "Failed to render $child" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
is Composite -> compositeFactory(obj)
|
is Composite -> compositeFactory(obj)
|
||||||
is Proxy -> proxyFactory(obj)
|
|
||||||
else -> {
|
else -> {
|
||||||
//find specialized factory for this type if it is present
|
//find specialized factory for this type if it is present
|
||||||
val factory = findObjectFactory(obj::class)
|
val factory: ThreeFactory<VisualObject3D>? = findObjectFactory(obj::class)
|
||||||
when {
|
when {
|
||||||
factory != null -> factory(obj)
|
factory != null -> factory(obj)
|
||||||
obj is Shape -> ThreeShapeFactory(obj)
|
obj is Shape -> ThreeShapeFactory(obj)
|
||||||
@ -62,30 +107,44 @@ class ThreePlugin : AbstractPlugin() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun buildCamera(meta: Meta) = PerspectiveCamera(
|
|
||||||
meta["fov"].int ?: 75,
|
|
||||||
meta["aspect"].double ?: 1.0,
|
|
||||||
meta["nearClip"].double ?: World.CAMERA_NEAR_CLIP,
|
|
||||||
meta["farClip"].double ?: World.CAMERA_FAR_CLIP
|
|
||||||
).apply {
|
|
||||||
position.setZ(World.CAMERA_INITIAL_DISTANCE)
|
|
||||||
rotation.set(
|
|
||||||
World.CAMERA_INITIAL_X_ANGLE,
|
|
||||||
World.CAMERA_INITIAL_Y_ANGLE,
|
|
||||||
World.CAMERA_INITIAL_Z_ANGLE
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addControls(camera: Camera, element: Node, meta: Meta) {
|
|
||||||
when (meta["type"].string) {
|
|
||||||
"trackball" -> TrackballControls(camera, element)
|
|
||||||
else -> OrbitControls(camera, element)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object : PluginFactory<ThreePlugin> {
|
companion object : PluginFactory<ThreePlugin> {
|
||||||
override val tag = PluginTag("visual.three", PluginTag.DATAFORGE_GROUP)
|
override val tag = PluginTag("visual.three", PluginTag.DATAFORGE_GROUP)
|
||||||
override val type = ThreePlugin::class
|
override val type = ThreePlugin::class
|
||||||
override fun invoke(meta: Meta) = ThreePlugin()
|
override fun invoke(meta: Meta, context: Context) = ThreePlugin()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal operator fun Object3D.set(token: NameToken, object3D: Object3D) {
|
||||||
|
object3D.name = token.toString()
|
||||||
|
add(object3D)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Object3D.getOrCreateGroup(name: Name): Object3D {
|
||||||
|
return when {
|
||||||
|
name.isEmpty() -> this
|
||||||
|
name.length == 1 -> {
|
||||||
|
val token = name.first()!!
|
||||||
|
children.find { it.name == token.toString() } ?: info.laht.threekt.objects.Group().also { group ->
|
||||||
|
group.name = token.toString()
|
||||||
|
this.add(group)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> getOrCreateGroup(name.first()!!.asName()).getOrCreateGroup(name.cutFirst())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal operator fun Object3D.set(name: Name, obj: Object3D) {
|
||||||
|
when (name.length) {
|
||||||
|
0 -> error("Can't set object with an empty name")
|
||||||
|
1 -> set(name.first()!!, obj)
|
||||||
|
else -> getOrCreateGroup(name.cutLast())[name.last()!!] = obj
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Object3D.findChild(name: Name): Object3D? {
|
||||||
|
return when {
|
||||||
|
name.isEmpty() -> this
|
||||||
|
name.length == 1 -> this.children.find { it.name == name.first()!!.toString() }
|
||||||
|
else -> findChild(name.first()!!.asName())?.findChild(name.cutFirst())
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,6 +1,8 @@
|
|||||||
package hep.dataforge.vis.spatial.three
|
package hep.dataforge.vis.spatial.three
|
||||||
|
|
||||||
|
import hep.dataforge.names.toName
|
||||||
import hep.dataforge.vis.spatial.Proxy
|
import hep.dataforge.vis.spatial.Proxy
|
||||||
|
import hep.dataforge.vis.spatial.Proxy.Companion.PROXY_CHILD_PROPERTY_PREFIX
|
||||||
import hep.dataforge.vis.spatial.VisualObject3D
|
import hep.dataforge.vis.spatial.VisualObject3D
|
||||||
import info.laht.threekt.core.Object3D
|
import info.laht.threekt.core.Object3D
|
||||||
|
|
||||||
@ -10,15 +12,27 @@ class ThreeProxyFactory(val three: ThreePlugin) : ThreeFactory<Proxy> {
|
|||||||
override val type = Proxy::class
|
override val type = Proxy::class
|
||||||
|
|
||||||
override fun invoke(obj: Proxy): Object3D {
|
override fun invoke(obj: Proxy): Object3D {
|
||||||
val template = obj.template
|
val template = obj.prototype
|
||||||
val cachedObject = cache.getOrPut(template) {
|
val cachedObject = cache.getOrPut(template) {
|
||||||
three.buildObject3D(template)
|
three.buildObject3D(template)
|
||||||
}
|
}
|
||||||
|
|
||||||
//val mesh = Mesh(templateMesh.geometry as BufferGeometry, templateMesh.material)
|
//val mesh = Mesh(templateMesh.geometry as BufferGeometry, templateMesh.material)
|
||||||
val mesh = cachedObject.clone()
|
val object3D = cachedObject.clone()
|
||||||
|
object3D.updatePosition(obj)
|
||||||
|
|
||||||
mesh.updatePosition(obj)
|
obj.onPropertyChange(this) { name, _, _ ->
|
||||||
return mesh
|
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 child = object3D.findChild(childName)?: error("Object child with name '$childName' not found")
|
||||||
|
child.updateProperty(proxyChild, propertyName)
|
||||||
|
} else {
|
||||||
|
object3D.updateProperty(obj, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return object3D
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
@file:UseSerializers(Point3DSerializer::class)
|
||||||
|
|
||||||
|
package hep.dataforge.vis.spatial.three
|
||||||
|
|
||||||
|
import hep.dataforge.io.serialization.ConfigSerializer
|
||||||
|
import hep.dataforge.meta.Config
|
||||||
|
import hep.dataforge.vis.common.AbstractVisualObject
|
||||||
|
import hep.dataforge.vis.spatial.Point3D
|
||||||
|
import hep.dataforge.vis.spatial.Point3DSerializer
|
||||||
|
import hep.dataforge.vis.spatial.VisualObject3D
|
||||||
|
import info.laht.threekt.core.Object3D
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.UseSerializers
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A custom visual object that has its own Three.js renderer
|
||||||
|
*/
|
||||||
|
interface ThreeVisualObject : VisualObject3D {
|
||||||
|
fun toObject3D(): Object3D
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class CustomThreeVisualObject(val threeFactory: ThreeFactory<VisualObject3D>) : AbstractVisualObject(),
|
||||||
|
ThreeVisualObject {
|
||||||
|
override var position: Point3D? = null
|
||||||
|
override var rotation: Point3D? = null
|
||||||
|
override var scale: Point3D? = null
|
||||||
|
|
||||||
|
@Serializable(ConfigSerializer::class)
|
||||||
|
override var properties: Config? = null
|
||||||
|
|
||||||
|
override fun toObject3D(): Object3D = threeFactory(this)
|
||||||
|
|
||||||
|
}
|
@ -16,6 +16,9 @@ import info.laht.threekt.math.Matrix4
|
|||||||
import info.laht.threekt.math.Vector3
|
import info.laht.threekt.math.Vector3
|
||||||
import info.laht.threekt.objects.Mesh
|
import info.laht.threekt.objects.Mesh
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructive Solid Geometry
|
||||||
|
*/
|
||||||
open external class CSG {
|
open external class CSG {
|
||||||
open var polygons: Array<Polygon>
|
open var polygons: Array<Polygon>
|
||||||
open fun clone(): CSG
|
open fun clone(): CSG
|
||||||
|
@ -0,0 +1,87 @@
|
|||||||
|
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.spatial.VisualGroup3D
|
||||||
|
import kotlinx.html.InputType
|
||||||
|
import kotlinx.html.TagConsumer
|
||||||
|
import kotlinx.html.button
|
||||||
|
import kotlinx.html.dom.append
|
||||||
|
import kotlinx.html.js.*
|
||||||
|
import org.w3c.dom.Element
|
||||||
|
import org.w3c.dom.HTMLElement
|
||||||
|
import org.w3c.dom.events.Event
|
||||||
|
import org.w3c.files.Blob
|
||||||
|
import org.w3c.files.BlobPropertyBag
|
||||||
|
import kotlin.dom.clear
|
||||||
|
|
||||||
|
private fun saveData(event: Event, fileName: String, mimeType: String = "text/plain", dataBuilder: () -> String) {
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
val fileSaver = requireJS("file-saver")
|
||||||
|
val blob = Blob(arrayOf(dataBuilder()), BlobPropertyBag("$mimeType;charset=utf-8"))
|
||||||
|
fileSaver.saveAs(blob, fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Element.displayCanvasControls(canvas: ThreeCanvas, block: TagConsumer<HTMLElement>.() -> Unit = {}) {
|
||||||
|
clear()
|
||||||
|
append {
|
||||||
|
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 ->
|
||||||
|
Visual3DPlugin.json.stringify(
|
||||||
|
VisualGroup3D.serializer(),
|
||||||
|
group
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (json != null) {
|
||||||
|
saveData(it, "object.json", "text/json"){
|
||||||
|
json
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
card("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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
block()
|
||||||
|
}
|
||||||
|
}
|
@ -6,21 +6,16 @@ import hep.dataforge.meta.get
|
|||||||
import hep.dataforge.meta.node
|
import hep.dataforge.meta.node
|
||||||
import hep.dataforge.vis.spatial.*
|
import hep.dataforge.vis.spatial.*
|
||||||
import info.laht.threekt.core.BufferGeometry
|
import info.laht.threekt.core.BufferGeometry
|
||||||
|
import info.laht.threekt.core.DirectGeometry
|
||||||
import info.laht.threekt.core.Face3
|
import info.laht.threekt.core.Face3
|
||||||
import info.laht.threekt.core.Geometry
|
import info.laht.threekt.core.Geometry
|
||||||
import info.laht.threekt.core.Object3D
|
import info.laht.threekt.external.controls.OrbitControls
|
||||||
|
import info.laht.threekt.materials.Material
|
||||||
import info.laht.threekt.math.Euler
|
import info.laht.threekt.math.Euler
|
||||||
import info.laht.threekt.math.Vector3
|
import info.laht.threekt.math.Vector3
|
||||||
|
import info.laht.threekt.objects.Mesh
|
||||||
/**
|
import info.laht.threekt.textures.Texture
|
||||||
* Utility methods for three.kt.
|
import kotlin.math.PI
|
||||||
* TODO move to three project
|
|
||||||
*/
|
|
||||||
|
|
||||||
@Suppress("FunctionName")
|
|
||||||
fun Group(children: Collection<Object3D>) = info.laht.threekt.objects.Group().apply {
|
|
||||||
children.forEach { this.add(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
val VisualObject3D.euler get() = Euler(rotationX, rotationY, rotationZ, rotationOrder.name)
|
val VisualObject3D.euler get() = Euler(rotationX, rotationY, rotationZ, rotationOrder.name)
|
||||||
|
|
||||||
@ -28,6 +23,8 @@ val MetaItem<*>.vector get() = Vector3(node["x"].float ?: 0f, node["y"].float ?:
|
|||||||
|
|
||||||
fun Geometry.toBufferGeometry(): BufferGeometry = BufferGeometry().apply { fromGeometry(this@toBufferGeometry) }
|
fun Geometry.toBufferGeometry(): BufferGeometry = BufferGeometry().apply { fromGeometry(this@toBufferGeometry) }
|
||||||
|
|
||||||
|
internal fun Double.toRadians() = this * PI / 180
|
||||||
|
|
||||||
fun CSG.toGeometry(): Geometry {
|
fun CSG.toGeometry(): Geometry {
|
||||||
val geom = Geometry()
|
val geom = Geometry()
|
||||||
|
|
||||||
@ -43,7 +40,7 @@ fun CSG.toGeometry(): Geometry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (j in 3..polygon.vertices.size) {
|
for (j in 3..polygon.vertices.size) {
|
||||||
val fc = Face3(v0, v0 + j - 2, v0 + j - 1, zero)
|
val fc = Face3(v0, v0 + j - 2, v0 + j - 1, World.ZERO)
|
||||||
fc.vertexNormals = arrayOf(
|
fc.vertexNormals = arrayOf(
|
||||||
Vector3().copy(pvs[0].normal),
|
Vector3().copy(pvs[0].normal),
|
||||||
Vector3().copy(pvs[j - 2].normal),
|
Vector3().copy(pvs[j - 2].normal),
|
||||||
@ -65,3 +62,18 @@ fun CSG.toGeometry(): Geometry {
|
|||||||
geom.computeBoundingBox()
|
geom.computeBoundingBox()
|
||||||
return geom
|
return geom
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun Any.dispose() {
|
||||||
|
when (this) {
|
||||||
|
is Geometry -> dispose()
|
||||||
|
is BufferGeometry -> dispose()
|
||||||
|
is DirectGeometry -> dispose()
|
||||||
|
is Material -> dispose()
|
||||||
|
is Mesh -> {
|
||||||
|
geometry.dispose()
|
||||||
|
material.dispose()
|
||||||
|
}
|
||||||
|
is OrbitControls -> dispose()
|
||||||
|
is Texture -> dispose()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,156 @@
|
|||||||
|
/*
|
||||||
|
* The MIT License
|
||||||
|
*
|
||||||
|
* Copyright 2017-2018 Lars Ivar Hatledal
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@file:JsModule("three")
|
||||||
|
@file:JsNonModule
|
||||||
|
|
||||||
|
package info.laht.threekt
|
||||||
|
|
||||||
|
|
||||||
|
external val REVISION: String
|
||||||
|
external val CullFaceNone: Int
|
||||||
|
external val CullFaceBack: Int
|
||||||
|
external val CullFaceFront: Int
|
||||||
|
external val CullFaceFrontBack: Int
|
||||||
|
external val FrontFaceDirectionCW: Int
|
||||||
|
external val FrontFaceDirectionCCW: Int
|
||||||
|
external val BasicShadowMap: Int
|
||||||
|
external val PCFShadowMap: Int
|
||||||
|
external val PCFSoftShadowMap: Int
|
||||||
|
external val FrontSide: Int
|
||||||
|
external val BackSide: Int
|
||||||
|
external val DoubleSide: Int
|
||||||
|
external val FlatShading: Int
|
||||||
|
external val SmoothShading: Int
|
||||||
|
external val NoColors: Int
|
||||||
|
external val FaceColors: Int
|
||||||
|
external val VertexColors: Int
|
||||||
|
external val NoBlending: Int
|
||||||
|
external val NormalBlending: Int
|
||||||
|
external val AdditiveBlending: Int
|
||||||
|
external val SubtractiveBlending: Int
|
||||||
|
external val MultiplyBlending: Int
|
||||||
|
external val CustomBlending: Int
|
||||||
|
external val AddEquation: Int
|
||||||
|
external val SubtractEquation: Int
|
||||||
|
external val ReverseSubtractEquation: Int
|
||||||
|
external val MinEquation: Int
|
||||||
|
external val MaxEquation: Int
|
||||||
|
external val ZeroFactor: Int
|
||||||
|
external val OneFactor: Int
|
||||||
|
external val SrcColorFactor: Int
|
||||||
|
external val OneMinusSrcColorFactor: Int
|
||||||
|
external val SrcAlphaFactor: Int
|
||||||
|
external val OneMinusSrcAlphaFactor: Int
|
||||||
|
external val DstAlphaFactor: Int
|
||||||
|
external val OneMinusDstAlphaFactor: Int
|
||||||
|
external val DstColorFactor: Int
|
||||||
|
external val OneMinusDstColorFactor: Int
|
||||||
|
external val SrcAlphaSaturateFactor: Int
|
||||||
|
external val NeverDepth: Int
|
||||||
|
external val AlwaysDepth: Int
|
||||||
|
external val LessDepth: Int
|
||||||
|
external val LessEqualDepth: Int
|
||||||
|
external val EqualDepth: Int
|
||||||
|
external val GreaterEqualDepth: Int
|
||||||
|
external val GreaterDepth: Int
|
||||||
|
external val NotEqualDepth: Int
|
||||||
|
external val MultiplyOperation: Int
|
||||||
|
external val MixOperation: Int
|
||||||
|
external val AddOperation: Int
|
||||||
|
external val NoToneMapping: Int
|
||||||
|
external val LinearToneMapping: Int
|
||||||
|
external val ReinhardToneMapping: Int
|
||||||
|
external val Uncharted2ToneMapping: Int
|
||||||
|
external val CineonToneMapping: Int
|
||||||
|
external val UVMapping: Int
|
||||||
|
external val CubeReflectionMapping: Int
|
||||||
|
external val CubeRefractionMapping: Int
|
||||||
|
external val EquirectangularReflectionMapping: Int
|
||||||
|
external val EquirectangularRefractionMapping: Int
|
||||||
|
external val SphericalReflectionMapping: Int
|
||||||
|
external val CubeUVReflectionMapping: Int
|
||||||
|
external val CubeUVRefractionMapping: Int
|
||||||
|
external val RepeatWrapping: Int
|
||||||
|
external val ClampToEdgeWrapping: Int
|
||||||
|
external val MirroredRepeatWrapping: Int
|
||||||
|
external val NearestFilter: Int
|
||||||
|
external val NearestMipMapNearestFilter: Int
|
||||||
|
external val NearestMipMapLinearFilter: Int
|
||||||
|
external val LinearFilter: Int
|
||||||
|
external val LinearMipMapNearestFilter: Int
|
||||||
|
external val LinearMipMapLinearFilter: Int
|
||||||
|
external val UnsignedByteType: Int
|
||||||
|
external val ByteType: Int
|
||||||
|
external val ShortType: Int
|
||||||
|
external val UnsignedShortType: Int
|
||||||
|
external val IntType: Int
|
||||||
|
external val UnsignedIntType: Int
|
||||||
|
external val FloatType: Int
|
||||||
|
external val HalfFloatType: Int
|
||||||
|
external val UnsignedShort4444Type: Int
|
||||||
|
external val UnsignedShort5551Type: Int
|
||||||
|
external val UnsignedShort565Type: Int
|
||||||
|
external val UnsignedInt248Type: Int
|
||||||
|
external val AlphaFormat: Int
|
||||||
|
external val RGBFormat: Int
|
||||||
|
external val RGBAFormat: Int
|
||||||
|
external val LuminanceFormat: Int
|
||||||
|
external val LuminanceAlphaFormat: Int
|
||||||
|
external val RGBEFormat: Int
|
||||||
|
external val DepthFormat: Int
|
||||||
|
external val DepthStencilFormat: Int
|
||||||
|
external val RGB_S3TC_DXT1_Format: Int
|
||||||
|
external val RGBA_S3TC_DXT1_Format: Int
|
||||||
|
external val RGBA_S3TC_DXT3_Format: Int
|
||||||
|
external val RGBA_S3TC_DXT5_Format: Int
|
||||||
|
external val RGB_PVRTC_4BPPV1_Format: Int
|
||||||
|
external val RGB_PVRTC_2BPPV1_Format: Int
|
||||||
|
external val RGBA_PVRTC_4BPPV1_Format: Int
|
||||||
|
external val RGBA_PVRTC_2BPPV1_Format: Int
|
||||||
|
external val RGB_ETC1_Format: Int
|
||||||
|
external val LoopOnce: Int
|
||||||
|
external val LoopRepeat: Int
|
||||||
|
external val LoopPingPong: Int
|
||||||
|
external val InterpolateDiscrete: Int
|
||||||
|
external val InterpolateLinear: Int
|
||||||
|
external val InterpolateSmooth: Int
|
||||||
|
external val ZeroCurvatureEnding: Int
|
||||||
|
external val ZeroSlopeEnding: Int
|
||||||
|
external val WrapAroundEnding: Int
|
||||||
|
external val TrianglesDrawMode: Int
|
||||||
|
external val TriangleStripDrawMode: Int
|
||||||
|
external val TriangleFanDrawMode: Int
|
||||||
|
external val LinearEncoding: Int
|
||||||
|
external val sRGBEncoding: Int
|
||||||
|
external val GammaEncoding: Int
|
||||||
|
external val RGBEEncoding: Int
|
||||||
|
external val LogLuvEncoding: Int
|
||||||
|
external val RGBM7Encoding: Int
|
||||||
|
external val RGBM16Encoding: Int
|
||||||
|
external val RGBDEncoding: Int
|
||||||
|
external val BasicDepthPacking: Int
|
||||||
|
external val RGBADepthPacking: Int
|
||||||
|
|
||||||
|
|
@ -0,0 +1,128 @@
|
|||||||
|
@file:JsModule("three")
|
||||||
|
@file:JsNonModule
|
||||||
|
|
||||||
|
package info.laht.threekt.animation
|
||||||
|
|
||||||
|
import info.laht.threekt.core.Object3D
|
||||||
|
|
||||||
|
external class AnimationAction(
|
||||||
|
mixer: AnimationMixer,
|
||||||
|
clip: AnimationClip,
|
||||||
|
localRoot: Object3D
|
||||||
|
) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setting enabled to false disables this action, so that it has no impact. Default is true.
|
||||||
|
*
|
||||||
|
* When the action is re-enabled, the animation continues from its current time
|
||||||
|
* (setting enabled to false doesn't reset the action).
|
||||||
|
*
|
||||||
|
* Note: Setting enabled to true doesn’t automatically restart the animation.
|
||||||
|
* Setting enabled to true will only restart the animation immediately if the following condition
|
||||||
|
* is fulfilled: paused is false, this action has not been deactivated in the meantime
|
||||||
|
* (by executing a stop or reset command), and neither weight nor timeScale is 0.
|
||||||
|
*/
|
||||||
|
var enabled: Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The looping mode (can be changed with setLoop). Default is THREE.LoopRepeat (with an infinite number of repetitions)
|
||||||
|
*/
|
||||||
|
var loop: Int
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setting paused to true pauses the execution of the action by setting the effective time scale to 0. Default is false.
|
||||||
|
*/
|
||||||
|
var paused: Boolean
|
||||||
|
|
||||||
|
var repetitions: Int
|
||||||
|
|
||||||
|
var time: Double
|
||||||
|
|
||||||
|
var timeScale: Double
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The degree of influence of this action (in the interval [0, 1]).
|
||||||
|
* Values between 0 (no impact) and 1 (full impact) can be used to blend between several actions. Default is 1.
|
||||||
|
*
|
||||||
|
* Properties/methods concerning weight are: crossFadeFrom, crossFadeTo, enabled, fadeIn, fadeOut,
|
||||||
|
* getEffectiveWeight, setEffectiveWeight, stopFading.
|
||||||
|
*/
|
||||||
|
var weight: Double
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables smooth interpolation without separate clips for start, loop and end. Default is true.
|
||||||
|
*/
|
||||||
|
var zeroSlopeAtEnd: Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables smooth interpolation without separate clips for start, loop and end. Default is true.
|
||||||
|
*/
|
||||||
|
var zeroSlopeAtStart: Boolean
|
||||||
|
|
||||||
|
|
||||||
|
// State & Scheduling
|
||||||
|
|
||||||
|
fun play(): AnimationAction
|
||||||
|
|
||||||
|
fun stop(): AnimationAction
|
||||||
|
|
||||||
|
fun reset(): AnimationAction
|
||||||
|
|
||||||
|
fun isRunning(): Boolean
|
||||||
|
|
||||||
|
// return true when play has been called
|
||||||
|
fun isScheduled(): Boolean
|
||||||
|
|
||||||
|
fun startAt(time: Number): AnimationAction
|
||||||
|
fun setLoop(mode: Int, repetitions: Int): AnimationAction
|
||||||
|
|
||||||
|
// Weight
|
||||||
|
|
||||||
|
// set the weight stopping any scheduled fading
|
||||||
|
// although .enabled = false yields an effective weight of zero, this
|
||||||
|
// method does *not* change .enabled, because it would be confusing
|
||||||
|
fun setEffectiveWeight(weight: Number)
|
||||||
|
|
||||||
|
// return the weight considering fading and .enabled
|
||||||
|
fun getEffectiveWeight()
|
||||||
|
|
||||||
|
fun fadeIn(duration: Number): AnimationAction
|
||||||
|
|
||||||
|
fun fadeOut(duration: Number): AnimationAction
|
||||||
|
|
||||||
|
fun crossFadeFrom(fadeOutAction: AnimationAction, duration: Number, warp: Boolean)
|
||||||
|
|
||||||
|
fun crossFadeTo(fadeInAction: AnimationAction, duration: Number, warp: Boolean)
|
||||||
|
|
||||||
|
fun stopFading(): AnimationAction
|
||||||
|
|
||||||
|
// Time Scale Control
|
||||||
|
|
||||||
|
// set the time scale stopping any scheduled warping
|
||||||
|
// although .paused = true yields an effective time scale of zero, this
|
||||||
|
// method does *not* change .paused, because it would be confusing
|
||||||
|
fun setEffectiveTimeScale(timeScale: Number): AnimationAction
|
||||||
|
|
||||||
|
// return the time scale considering warping and .paused
|
||||||
|
fun getEffectiveTimeScale(): Double
|
||||||
|
|
||||||
|
fun setDuration(duration: Number): AnimationAction
|
||||||
|
|
||||||
|
fun syncWith(action: AnimationAction)
|
||||||
|
|
||||||
|
fun halt(duration: Number): AnimationAction
|
||||||
|
|
||||||
|
fun warp(startTimeScale: Number, endTimeScale: Number, duration: Number): AnimationAction
|
||||||
|
|
||||||
|
fun stopWarping(): AnimationAction
|
||||||
|
|
||||||
|
// Object Accessors
|
||||||
|
|
||||||
|
fun getMixer(): AnimationMixer
|
||||||
|
|
||||||
|
fun getClip(): AnimationClip
|
||||||
|
|
||||||
|
fun getRoot(): Object3D
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
@file:JsModule("three")
|
||||||
|
@file:JsNonModule
|
||||||
|
|
||||||
|
package info.laht.threekt.animation
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An AnimationClip is a reusable set of keyframe tracks which represent an animation.
|
||||||
|
|
||||||
|
For an overview of the different elements of the three.js animation system see the "Animation System" article in the "Next Steps" section of the manual.
|
||||||
|
*
|
||||||
|
* @param name a name for this clip.
|
||||||
|
* @param duration the duration of this clip (in seconds). If a negative value is passed, the duration will be calculated from the passed tracks array.
|
||||||
|
* @param tracks an array of KeyframeTracks.
|
||||||
|
*/
|
||||||
|
external class AnimationClip(
|
||||||
|
name: String,
|
||||||
|
duration: Number,
|
||||||
|
tracks: Array<KeyFrameTrack>
|
||||||
|
) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Parses the animation.hierarchy format and returns an AnimationClip.
|
||||||
|
*/
|
||||||
|
fun parse(json: String): AnimationClip
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes an AnimationClip and returns a JSON object.
|
||||||
|
*/
|
||||||
|
fun toJSON(clip: AnimationClip): dynamic
|
||||||
|
}
|
||||||
|
|
||||||
|
var uuid: String
|
||||||
|
var name: String
|
||||||
|
var duration: Double
|
||||||
|
var tracks: Array<KeyFrameTrack>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optimizes each track by removing equivalent sequential keys (which are common in morph target sequences).
|
||||||
|
*/
|
||||||
|
fun optimize(): AnimationClip
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the duration of the clip to the duration of its longest KeyframeTrack.
|
||||||
|
*/
|
||||||
|
fun resetDuration()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trims all tracks to the clip's duration.
|
||||||
|
*/
|
||||||
|
fun trim(): AnimationClip
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
@file:JsModule("three")
|
||||||
|
@file:JsNonModule
|
||||||
|
|
||||||
|
package info.laht.threekt.animation
|
||||||
|
|
||||||
|
import info.laht.threekt.core.Object3D
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The AnimationMixer is a player for animations on a particular object in the scene.
|
||||||
|
* When multiple objects in the scene are animated independently, one AnimationMixer may be used for each object.
|
||||||
|
|
||||||
|
* For an overview of the different elements of the three.js animation system see the "Animation System" article
|
||||||
|
* in the "Next Steps" section of the manual.
|
||||||
|
*/
|
||||||
|
external class AnimationMixer(
|
||||||
|
root: Object3D
|
||||||
|
) {
|
||||||
|
|
||||||
|
var time: Double
|
||||||
|
var timeScale: Double
|
||||||
|
|
||||||
|
fun clipAction(clip: AnimationClip, optionalRoot: Object3D = definedExternally): AnimationClip
|
||||||
|
fun existingAction(clip: AnimationClip, optionalRoot: Object3D = definedExternally): AnimationClip
|
||||||
|
|
||||||
|
fun getRoot(): Object3D
|
||||||
|
|
||||||
|
fun stopAllAction()
|
||||||
|
|
||||||
|
fun update(deltaTimeInSeconds: Number)
|
||||||
|
|
||||||
|
fun uncacheClip(clip: AnimationClip)
|
||||||
|
|
||||||
|
fun unchacheRoot(root: Object3D)
|
||||||
|
|
||||||
|
fun uncacheAction(clip: AnimationClip, optionalRoot: Object3D)
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
@file:JsModule("three")
|
||||||
|
@file:JsNonModule
|
||||||
|
|
||||||
|
package info.laht.threekt.animation
|
||||||
|
|
||||||
|
external object AnimationUtils {
|
||||||
|
//TODO
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user