forked from kscience/visionforge
Merge branch 'dev' into doc
# Conflicts: # README.md # dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/Colors.kt # dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualGroup.kt # dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualObject.kt # dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/editor/jsVisualTree.kt
This commit is contained in:
commit
6bb6a82b09
@ -1,4 +1,6 @@
|
|||||||
# DataForge Plugins for Visualisation
|
[![JetBrains Research](https://jb.gg/badges/research.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub)
|
||||||
|
|
||||||
|
# DataForge plugins for visualisation
|
||||||
|
|
||||||
This repository contains [DataForge](http://npm.mipt.ru/dataforge/)
|
This repository contains [DataForge](http://npm.mipt.ru/dataforge/)
|
||||||
(also [here](https://github.com/mipt-npm/dataforge-core)) components useful for visualization in
|
(also [here](https://github.com/mipt-npm/dataforge-core)) components useful for visualization in
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
val dataforgeVersion by extra("0.1.3")
|
import scientifik.useSerialization
|
||||||
|
|
||||||
|
val dataforgeVersion by extra("0.1.5-dev-6")
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
val kotlinVersion = "1.3.50"
|
val kotlinVersion = "1.3.61"
|
||||||
val toolsVersion = "0.2.0"
|
val toolsVersion = "0.3.2"
|
||||||
|
|
||||||
kotlin("jvm") version kotlinVersion apply false
|
kotlin("jvm") version kotlinVersion apply false
|
||||||
id("kotlin-dce-js") version kotlinVersion apply false
|
id("kotlin-dce-js") version kotlinVersion apply false
|
||||||
@ -15,14 +17,22 @@ plugins {
|
|||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
|
mavenLocal()
|
||||||
maven("https://dl.bintray.com/pdvrieze/maven")
|
maven("https://dl.bintray.com/pdvrieze/maven")
|
||||||
maven("http://maven.jzy3d.org/releases")
|
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,27 +1,50 @@
|
|||||||
|
import org.openjfx.gradle.JavaFXOptions
|
||||||
|
import scientifik.useSerialization
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("scientifik.mpp")
|
id("scientifik.mpp")
|
||||||
}
|
id("org.openjfx.javafxplugin")
|
||||||
|
|
||||||
scientifik{
|
|
||||||
withSerialization()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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"))
|
||||||
api("org.jetbrains:kotlin-extensions:1.0.1-pre.83-kotlin-1.3.50")
|
implementation(npm("jsoneditor"))
|
||||||
api(npm("core-js"))
|
implementation(npm("file-saver"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
configure<JavaFXOptions> {
|
||||||
|
modules("javafx.controls")
|
||||||
|
}
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
package hep.dataforge.vis.common
|
package hep.dataforge.vis.common
|
||||||
|
|
||||||
import hep.dataforge.meta.Meta
|
|
||||||
import hep.dataforge.meta.MetaBuilder
|
|
||||||
import hep.dataforge.meta.MetaItem
|
import hep.dataforge.meta.MetaItem
|
||||||
import hep.dataforge.names.*
|
import hep.dataforge.names.*
|
||||||
import kotlinx.serialization.Transient
|
import kotlinx.serialization.Transient
|
||||||
@ -17,46 +15,7 @@ abstract class AbstractVisualGroup : AbstractVisualObject(), MutableVisualGroup
|
|||||||
/**
|
/**
|
||||||
* A map of top level named children
|
* A map of top level named children
|
||||||
*/
|
*/
|
||||||
abstract override val children: Map<NameToken, VisualObject> //get() = _children
|
abstract override val children: Map<NameToken, VisualObject>
|
||||||
|
|
||||||
/**
|
|
||||||
* Styles, defined in this group. A style could be defined but not applied
|
|
||||||
* TODO replace by custom object with get/set functionality
|
|
||||||
*/
|
|
||||||
protected abstract val styleSheet: MutableMap<Name, Meta>
|
|
||||||
|
|
||||||
override fun getStyle(name: Name): Meta? = styleSheet[name]
|
|
||||||
|
|
||||||
override fun addStyle(name: Name, meta: Meta, apply: Boolean) {
|
|
||||||
fun VisualObject.applyStyle(name: Name, meta: Meta) {
|
|
||||||
if (styles.contains(name)) {
|
|
||||||
//full update
|
|
||||||
//TODO do a fine grained update
|
|
||||||
if (this is AbstractVisualObject) {
|
|
||||||
styleChanged()
|
|
||||||
} else {
|
|
||||||
propertyChanged(EmptyName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this is VisualGroup) {
|
|
||||||
this.children.forEach { (_, child) ->
|
|
||||||
child.applyStyle(name, meta)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
styleSheet[name] = meta
|
|
||||||
if (apply) {
|
|
||||||
applyStyle(name, meta)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// init {
|
|
||||||
// //Do after deserialization
|
|
||||||
// children.values.forEach {
|
|
||||||
// it.parent = this
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
override fun propertyChanged(name: Name, before: MetaItem<*>?, after: MetaItem<*>?) {
|
override fun propertyChanged(name: Name, before: MetaItem<*>?, after: MetaItem<*>?) {
|
||||||
super.propertyChanged(name, before, after)
|
super.propertyChanged(name, before, after)
|
||||||
@ -114,7 +73,7 @@ abstract class AbstractVisualGroup : AbstractVisualObject(), MutableVisualGroup
|
|||||||
protected abstract fun createGroup(name: Name): MutableVisualGroup
|
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
|
* Add named or unnamed child to the group. If key is null the child is considered unnamed. Both key and value are not
|
||||||
* allowed to be null in the same time. If name is present and [child] is null, the appropriate element is removed.
|
* allowed to be null in the same time. If name is present and [child] is null, the appropriate element is removed.
|
||||||
*/
|
*/
|
||||||
override fun set(name: Name, child: VisualObject?) {
|
override fun set(name: Name, child: VisualObject?) {
|
||||||
@ -141,22 +100,13 @@ abstract class AbstractVisualGroup : AbstractVisualObject(), MutableVisualGroup
|
|||||||
structureChangeListeners.forEach { it.callback(name, child) }
|
structureChangeListeners.forEach { it.callback(name, child) }
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun set(key: String, child: VisualObject?) = if (key.isBlank()) {
|
operator fun set(key: String, child: VisualObject?): Unit {
|
||||||
child?.let { addStatic(child) }
|
if (key.isBlank()) {
|
||||||
|
if(child!= null) {
|
||||||
|
addStatic(child)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
set(key.asName(), child)
|
set(key.toName(), child)
|
||||||
}
|
|
||||||
|
|
||||||
// operator fun set(key: String?, child: VisualObject?) = set(key ?: "", child)
|
|
||||||
|
|
||||||
protected fun MetaBuilder.updateChildren() {
|
|
||||||
//adding named children
|
|
||||||
children.forEach {
|
|
||||||
"children[${it.key}]" to it.value.toMeta()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun MetaBuilder.updateMeta() {
|
|
||||||
updateChildren()
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,9 +1,8 @@
|
|||||||
package hep.dataforge.vis.common
|
package hep.dataforge.vis.common
|
||||||
|
|
||||||
import hep.dataforge.meta.*
|
import hep.dataforge.meta.*
|
||||||
import hep.dataforge.names.EmptyName
|
|
||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.Name
|
||||||
import hep.dataforge.names.toName
|
import hep.dataforge.names.asName
|
||||||
import hep.dataforge.vis.common.VisualObject.Companion.STYLE_KEY
|
import hep.dataforge.vis.common.VisualObject.Companion.STYLE_KEY
|
||||||
import kotlinx.serialization.Transient
|
import kotlinx.serialization.Transient
|
||||||
|
|
||||||
@ -17,13 +16,23 @@ abstract class AbstractVisualObject : VisualObject {
|
|||||||
@Transient
|
@Transient
|
||||||
override var parent: VisualObject? = null
|
override var parent: VisualObject? = null
|
||||||
|
|
||||||
abstract override var properties: Config?
|
protected abstract var properties: Config?
|
||||||
|
|
||||||
override var styles: List<Name>
|
override var styles: List<String>
|
||||||
get() = properties?.get(STYLE_KEY).stringList.map(String::toName)
|
get() = properties?.get(STYLE_KEY).stringList
|
||||||
set(value) {
|
set(value) {
|
||||||
setProperty(STYLE_KEY, value.map { it.toString() })
|
//val allStyles = (field + value).distinct()
|
||||||
styleChanged()
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -39,10 +48,12 @@ abstract class AbstractVisualObject : VisualObject {
|
|||||||
private val listeners = HashSet<PropertyListener>()
|
private val listeners = HashSet<PropertyListener>()
|
||||||
|
|
||||||
override fun propertyChanged(name: Name, before: MetaItem<*>?, after: MetaItem<*>?) {
|
override fun propertyChanged(name: Name, before: MetaItem<*>?, after: MetaItem<*>?) {
|
||||||
|
if (before != after) {
|
||||||
for (l in listeners) {
|
for (l in listeners) {
|
||||||
l.action(name, before, after)
|
l.action(name, before, after)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onPropertyChange(owner: Any?, action: (Name, before: MetaItem<*>?, after: MetaItem<*>?) -> Unit) {
|
override fun onPropertyChange(owner: Any?, action: (Name, before: MetaItem<*>?, after: MetaItem<*>?) -> Unit) {
|
||||||
listeners.add(PropertyListener(owner, action))
|
listeners.add(PropertyListener(owner, action))
|
||||||
@ -52,10 +63,6 @@ abstract class AbstractVisualObject : VisualObject {
|
|||||||
listeners.removeAll { it.owner == owner }
|
listeners.removeAll { it.owner == owner }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setProperty(name: Name, value: Any?) {
|
|
||||||
config[name] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
private var styleCache: Meta? = null
|
private var styleCache: Meta? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -66,14 +73,7 @@ abstract class AbstractVisualObject : VisualObject {
|
|||||||
styleCache = it
|
styleCache = it
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun allProperties(): Laminate = Laminate(properties, mergedStyles)
|
||||||
/**
|
|
||||||
* Helper to reset style cache
|
|
||||||
*/
|
|
||||||
protected fun styleChanged() {
|
|
||||||
styleCache = null
|
|
||||||
propertyChanged(EmptyName)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? {
|
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? {
|
||||||
return if (inherit) {
|
return if (inherit) {
|
||||||
@ -82,20 +82,12 @@ abstract class AbstractVisualObject : VisualObject {
|
|||||||
properties?.get(name) ?: mergedStyles[name]
|
properties?.get(name) ?: mergedStyles[name]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun MetaBuilder.updateMeta() {}
|
|
||||||
|
|
||||||
override fun toMeta(): Meta = buildMeta {
|
|
||||||
"type" to this::class.simpleName
|
|
||||||
"properties" to properties
|
|
||||||
updateMeta()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun VisualObject.findStyle(styleName: Name): Meta? {
|
//fun VisualObject.findStyle(styleName: Name): Meta? {
|
||||||
if (this is VisualGroup) {
|
// if (this is VisualGroup) {
|
||||||
val style = getStyle(styleName)
|
// val style = resolveStyle(styleName)
|
||||||
if (style != null) return style
|
// if (style != null) return style
|
||||||
}
|
// }
|
||||||
return parent?.findStyle(styleName)
|
// return parent?.findStyle(styleName)
|
||||||
}
|
//}
|
@ -1,5 +1,8 @@
|
|||||||
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
|
import kotlin.math.max
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -178,6 +181,33 @@ object Colors {
|
|||||||
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
|
* Convert Int color to string of format #rrggbb
|
||||||
*/
|
*/
|
||||||
@ -200,4 +230,13 @@ object Colors {
|
|||||||
append(colorToString(blue))
|
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,6 +1,5 @@
|
|||||||
package hep.dataforge.vis.common
|
package hep.dataforge.vis.common
|
||||||
|
|
||||||
import hep.dataforge.meta.Meta
|
|
||||||
import hep.dataforge.names.*
|
import hep.dataforge.names.*
|
||||||
import hep.dataforge.provider.Provider
|
import hep.dataforge.provider.Provider
|
||||||
|
|
||||||
@ -15,6 +14,8 @@ interface VisualGroup : Provider, Iterable<VisualObject>, VisualObject {
|
|||||||
|
|
||||||
override val defaultTarget: String get() = VisualObject.TYPE
|
override val defaultTarget: String get() = VisualObject.TYPE
|
||||||
|
|
||||||
|
val styleSheet: StyleSheet?
|
||||||
|
|
||||||
override fun provideTop(target: String): Map<Name, Any> =
|
override fun provideTop(target: String): Map<Name, Any> =
|
||||||
when (target) {
|
when (target) {
|
||||||
VisualObject.TYPE -> children.flatMap { (key, value) ->
|
VisualObject.TYPE -> children.flatMap { (key, value) ->
|
||||||
@ -25,7 +26,7 @@ interface VisualGroup : Provider, Iterable<VisualObject>, VisualObject {
|
|||||||
}
|
}
|
||||||
res.entries
|
res.entries
|
||||||
}.associate { it.toPair() }
|
}.associate { it.toPair() }
|
||||||
//TODO add styles
|
STYLE_TARGET -> styleSheet?.items?.mapKeys { it.key.toName() } ?: emptyMap()
|
||||||
else -> emptyMap()
|
else -> emptyMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,17 +36,6 @@ interface VisualGroup : Provider, Iterable<VisualObject>, VisualObject {
|
|||||||
*/
|
*/
|
||||||
override fun iterator(): Iterator<VisualObject> = children.values.iterator()
|
override fun iterator(): Iterator<VisualObject> = children.values.iterator()
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve style by its name
|
|
||||||
* TODO change to Config?
|
|
||||||
*/
|
|
||||||
fun getStyle(name: Name): Meta?
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add or replace style with given name
|
|
||||||
*/
|
|
||||||
fun addStyle(name: Name, meta: Meta, apply: Boolean = true)
|
|
||||||
|
|
||||||
operator fun get(name: Name): VisualObject? {
|
operator fun get(name: Name): VisualObject? {
|
||||||
return when {
|
return when {
|
||||||
name.isEmpty() -> this
|
name.isEmpty() -> this
|
||||||
@ -53,7 +43,26 @@ interface VisualGroup : Provider, Iterable<VisualObject>, VisualObject {
|
|||||||
else -> (children[name.first()!!] as? VisualGroup)?.get(name.cutFirst())
|
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]
|
* Mutable version of [VisualGroup]
|
||||||
@ -75,4 +84,6 @@ interface MutableVisualGroup : VisualGroup {
|
|||||||
operator fun set(name: Name, child: VisualObject?)
|
operator fun set(name: Name, child: VisualObject?)
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun VisualGroup.get(str: String?) = get(str?.toName() ?: EmptyName)
|
operator fun VisualGroup.get(str: String?) = get(str?.toName() ?: Name.EMPTY)
|
||||||
|
|
||||||
|
fun MutableVisualGroup.removeAll() = children.keys.map { it.asName() }.forEach { this[it] = null }
|
@ -15,7 +15,7 @@ import kotlinx.serialization.Transient
|
|||||||
* 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.
|
||||||
@ -24,14 +24,16 @@ interface VisualObject : MetaRepr, Configurable {
|
|||||||
var parent: VisualObject?
|
var parent: VisualObject?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Direct properties access
|
* All properties including styles and prototypes if present, but without inheritance
|
||||||
*/
|
*/
|
||||||
val properties: Config?
|
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
|
||||||
@ -39,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
|
||||||
@ -54,11 +58,9 @@ interface VisualObject : MetaRepr, Configurable {
|
|||||||
fun removeChangeListener(owner: Any?)
|
fun removeChangeListener(owner: Any?)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of names of styles applied to this object
|
* List of names of styles applied to this object. Order matters. Not inherited
|
||||||
*/
|
*/
|
||||||
var styles: List<Name>
|
var styles: List<String>
|
||||||
|
|
||||||
fun findAllStyles(): Laminate = Laminate(styles.distinct().mapNotNull(::findStyle))
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val TYPE = "visual"
|
const val TYPE = "visual"
|
||||||
@ -66,6 +68,7 @@ interface VisualObject : MetaRepr, Configurable {
|
|||||||
|
|
||||||
//const val META_KEY = "@meta"
|
//const val META_KEY = "@meta"
|
||||||
//const val TAGS_KEY = "@tags"
|
//const val TAGS_KEY = "@tags"
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,8 +83,33 @@ fun VisualObject.getProperty(key: String, inherit: Boolean = true): MetaItem<*>?
|
|||||||
fun VisualObject.setProperty(key: String, value: Any?) = setProperty(key.toName(), value)
|
fun VisualObject.setProperty(key: String, value: Any?) = setProperty(key.toName(), value)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply style to [VisualObject] by adding it to the [style] list
|
* Add style name to the list of styles to be resolved later. The style with given name does not necessary exist at the moment.
|
||||||
*/
|
*/
|
||||||
fun VisualObject.applyStyle(name: String) {
|
fun VisualObject.useStyle(name: String) {
|
||||||
styles = styles + name.toName()
|
styles = styles + name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//private tailrec fun VisualObject.topGroup(): VisualGroup? {
|
||||||
|
// val parent = this.parent
|
||||||
|
// return if (parent == null) {
|
||||||
|
// this as? VisualGroup
|
||||||
|
// }
|
||||||
|
// else {
|
||||||
|
// parent.topGroup()
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
///**
|
||||||
|
// * Add or update given style on a top-most reachable parent group and apply it to this object
|
||||||
|
// */
|
||||||
|
//fun VisualObject.useStyle(name: String, builder: MetaBuilder.() -> Unit) {
|
||||||
|
// val styleName = name.toName()
|
||||||
|
// topGroup()?.updateStyle(styleName, builder) ?: error("Can't find parent group for $this")
|
||||||
|
// useStyle(styleName)
|
||||||
|
//}
|
||||||
|
|
||||||
|
tailrec fun VisualObject.findStyle(name: String): Meta? =
|
||||||
|
(this as? VisualGroup)?.styleSheet?.get(name) ?: parent?.findStyle(name)
|
||||||
|
|
||||||
|
fun VisualObject.findAllStyles(): Laminate = Laminate(styles.mapNotNull(::findStyle))
|
||||||
|
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,10 @@
|
|||||||
package hep.dataforge.vis
|
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()
|
@ -1,4 +1,7 @@
|
|||||||
package hep.dataforge.vis
|
package hep.dataforge.js
|
||||||
|
|
||||||
|
@JsName("require")
|
||||||
|
external fun requireJS(name: String): dynamic
|
||||||
|
|
||||||
inline fun <T : Any> jsObject(builder: T.() -> Unit): T {
|
inline fun <T : Any> jsObject(builder: T.() -> Unit): T {
|
||||||
val obj: T = js("({})") as T
|
val obj: T = js("({})") as T
|
@ -1,4 +1,4 @@
|
|||||||
package hep.dataforge.vis.spatial.editor
|
package hep.dataforge.vis.js.editor
|
||||||
|
|
||||||
import kotlinx.html.TagConsumer
|
import kotlinx.html.TagConsumer
|
||||||
import kotlinx.html.js.div
|
import kotlinx.html.js.div
|
@ -0,0 +1,74 @@
|
|||||||
|
package hep.dataforge.vis.js.editor
|
||||||
|
|
||||||
|
import hep.dataforge.names.NameToken
|
||||||
|
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.objectTree(
|
||||||
|
token: NameToken,
|
||||||
|
obj: VisualObject,
|
||||||
|
clickCallback: (VisualObject) -> Unit = {}
|
||||||
|
) {
|
||||||
|
clear()
|
||||||
|
append {
|
||||||
|
card("Object tree") {
|
||||||
|
subTree(token, obj, clickCallback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun TagConsumer<HTMLElement>.subTree(
|
||||||
|
token: NameToken,
|
||||||
|
obj: VisualObject,
|
||||||
|
clickCallback: (VisualObject) -> Unit
|
||||||
|
) {
|
||||||
|
|
||||||
|
if (obj is VisualGroup && !obj.isEmpty) {
|
||||||
|
lateinit var toggle: HTMLSpanElement
|
||||||
|
div("d-inline-block text-truncate") {
|
||||||
|
toggle = span("objTree-caret")
|
||||||
|
label("objTree-label") {
|
||||||
|
+token.toString()
|
||||||
|
onClickFunction = { clickCallback(obj) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val subtree = ul("objTree-subtree")
|
||||||
|
toggle.onclick = {
|
||||||
|
toggle.classList.toggle("objTree-caret-down")
|
||||||
|
subtree.apply {
|
||||||
|
if (toggle.classList.contains("objTree-caret-down")) {
|
||||||
|
obj.children.entries
|
||||||
|
.filter { !it.key.toString().startsWith("@") }
|
||||||
|
.sortedBy { (it.value as? VisualGroup)?.isEmpty ?: true }
|
||||||
|
.forEach { (token, child) ->
|
||||||
|
append {
|
||||||
|
li().apply {
|
||||||
|
subTree(token, child, clickCallback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//jQuery(subtree).asDynamic().collapse("toggle")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
div("d-inline-block text-truncate") {
|
||||||
|
span("objTree-leaf")
|
||||||
|
label("objTree-label") {
|
||||||
|
+token.toString()
|
||||||
|
onClickFunction = { clickCallback(obj) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -1,17 +1,12 @@
|
|||||||
package hep.dataforge.vis.spatial.editor
|
package hep.dataforge.vis.js.editor
|
||||||
|
|
||||||
import hep.dataforge.io.toJson
|
import hep.dataforge.io.toJson
|
||||||
import hep.dataforge.meta.*
|
import hep.dataforge.js.jsObject
|
||||||
|
import hep.dataforge.meta.DynamicMeta
|
||||||
|
import hep.dataforge.meta.Meta
|
||||||
|
import hep.dataforge.meta.update
|
||||||
import hep.dataforge.vis.common.VisualObject
|
import hep.dataforge.vis.common.VisualObject
|
||||||
import hep.dataforge.vis.common.findStyle
|
import hep.dataforge.vis.common.findStyle
|
||||||
import hep.dataforge.vis.jsObject
|
|
||||||
import hep.dataforge.vis.spatial.Material3D.Companion.COLOR_KEY
|
|
||||||
import hep.dataforge.vis.spatial.Material3D.Companion.OPACITY_KEY
|
|
||||||
import hep.dataforge.vis.spatial.VisualObject3D.Companion.VISIBLE_KEY
|
|
||||||
import hep.dataforge.vis.spatial.color
|
|
||||||
import hep.dataforge.vis.spatial.opacity
|
|
||||||
import hep.dataforge.vis.spatial.prototype
|
|
||||||
import hep.dataforge.vis.spatial.visible
|
|
||||||
import kotlinx.html.dom.append
|
import kotlinx.html.dom.append
|
||||||
import kotlinx.html.js.div
|
import kotlinx.html.js.div
|
||||||
import kotlinx.html.js.h4
|
import kotlinx.html.js.h4
|
||||||
@ -21,19 +16,13 @@ import kotlin.dom.clear
|
|||||||
//FIXME something rotten in JS-Meta converter
|
//FIXME something rotten in JS-Meta converter
|
||||||
fun Meta.toDynamic() = JSON.parse<dynamic>(toJson().toString())
|
fun Meta.toDynamic() = JSON.parse<dynamic>(toJson().toString())
|
||||||
|
|
||||||
|
//TODO add node descriptor instead of configuring property selector
|
||||||
fun Element.propertyEditor(item: VisualObject?, name: String?) {
|
fun Element.propertyEditor(item: VisualObject?, propertySelector: (VisualObject) -> Meta = { it.config }) {
|
||||||
clear()
|
clear()
|
||||||
if (item != null) {
|
if (item != null) {
|
||||||
append {
|
append {
|
||||||
card("Properties") {
|
card("Properties") {
|
||||||
val config = (item.properties ?: item.prototype?.properties) ?: EmptyMeta
|
val dMeta: dynamic = propertySelector(item).toDynamic()
|
||||||
val metaToEdit = config.builder().apply {
|
|
||||||
VISIBLE_KEY to (item.visible ?: true)
|
|
||||||
COLOR_KEY to (item.color ?: "#ffffff")
|
|
||||||
OPACITY_KEY to (item.opacity ?: 1.0)
|
|
||||||
}
|
|
||||||
val dMeta: dynamic = metaToEdit.toDynamic()
|
|
||||||
val options: JSONEditorOptions = jsObject {
|
val options: JSONEditorOptions = jsObject {
|
||||||
mode = "form"
|
mode = "form"
|
||||||
onChangeJSON = { item.config.update(DynamicMeta(it.asDynamic())) }
|
onChangeJSON = { item.config.update(DynamicMeta(it.asDynamic())) }
|
||||||
@ -46,13 +35,17 @@ fun Element.propertyEditor(item: VisualObject?, name: String?) {
|
|||||||
card("Styles") {
|
card("Styles") {
|
||||||
item.styles.forEach { style ->
|
item.styles.forEach { style ->
|
||||||
val styleMeta = item.findStyle(style)
|
val styleMeta = item.findStyle(style)
|
||||||
h4("container") { +style.toString() }
|
h4("container") { +style }
|
||||||
if (styleMeta != null) {
|
if (styleMeta != null) {
|
||||||
div("container").apply {
|
div("container").apply {
|
||||||
val options: JSONEditorOptions = jsObject {
|
val options: JSONEditorOptions = jsObject {
|
||||||
mode = "view"
|
mode = "view"
|
||||||
}
|
}
|
||||||
JSONEditor(this, options, styleMeta.toDynamic())
|
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
|
||||||
@ -40,8 +42,9 @@ class ColorValueChooser : ValueChooserBase<ColorPicker>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
|
||||||
id("scientifik.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 {
|
||||||
|
@ -2,10 +2,6 @@ plugins {
|
|||||||
id("scientifik.mpp")
|
id("scientifik.mpp")
|
||||||
}
|
}
|
||||||
|
|
||||||
scientifik{
|
|
||||||
withSerialization()
|
|
||||||
}
|
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
sourceSets {
|
sourceSets {
|
||||||
val commonMain by getting {
|
val commonMain by getting {
|
||||||
@ -14,11 +10,11 @@ kotlin {
|
|||||||
api("scientifik:gdml:0.1.4")
|
api("scientifik:gdml:0.1.4")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val jsMain by getting {
|
|
||||||
dependencies {
|
|
||||||
api(project(":dataforge-vis-spatial"))
|
|
||||||
//api("kotlin.js.externals:kotlin-js-jquery:3.2.0-0")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//tasks{
|
||||||
|
// val jsBrowserWebpack by getting(KotlinWebpack::class) {
|
||||||
|
// sourceMaps = false
|
||||||
|
// }
|
||||||
|
//}
|
@ -5,20 +5,15 @@ import hep.dataforge.meta.MetaBuilder
|
|||||||
import hep.dataforge.meta.buildMeta
|
import hep.dataforge.meta.buildMeta
|
||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.Name
|
||||||
import hep.dataforge.names.toName
|
import hep.dataforge.names.toName
|
||||||
import hep.dataforge.vis.common.Colors
|
import hep.dataforge.vis.common.useStyle
|
||||||
import hep.dataforge.vis.common.VisualObject
|
import hep.dataforge.vis.spatial.*
|
||||||
import hep.dataforge.vis.common.applyStyle
|
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_COLOR_KEY
|
||||||
import hep.dataforge.vis.spatial.Material3D.Companion.COLOR_KEY
|
|
||||||
import hep.dataforge.vis.spatial.RotationOrder
|
|
||||||
import hep.dataforge.vis.spatial.VisualGroup3D
|
|
||||||
import hep.dataforge.vis.spatial.VisualObject3D
|
|
||||||
import hep.dataforge.vis.spatial.rotationOrder
|
|
||||||
import scientifik.gdml.*
|
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,
|
||||||
@ -29,7 +24,7 @@ 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>()
|
private val styleCache = HashMap<Name, Meta>()
|
||||||
|
|
||||||
var lUnit: LUnit = LUnit.MM
|
var lUnit: LUnit = LUnit.MM
|
||||||
@ -38,13 +33,20 @@ class GDMLTransformer(val root: GDML) {
|
|||||||
var volumeAction: (GDMLGroup) -> Action = { Action.CACHE }
|
var volumeAction: (GDMLGroup) -> Action = { Action.CACHE }
|
||||||
|
|
||||||
|
|
||||||
var solidConfiguration: VisualObject3D.(parent: GDMLVolume, solid: GDMLSolid) -> Unit = { _, _ -> }
|
var solidConfiguration: VisualObject3D.(parent: GDMLVolume, solid: GDMLSolid) -> Unit = { parent, _ ->
|
||||||
|
lUnit = LUnit.CM
|
||||||
|
if (parent.physVolumes.isNotEmpty()) {
|
||||||
|
useStyle("opaque") {
|
||||||
|
Material3D.MATERIAL_OPACITY_KEY put 0.3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun VisualObject.useStyle(name: String, builder: MetaBuilder.() -> Unit) {
|
fun VisualObject3D.useStyle(name: String, builder: MetaBuilder.() -> Unit) {
|
||||||
styleCache.getOrPut(name.toName()) {
|
styleCache.getOrPut(name.toName()) {
|
||||||
buildMeta(builder)
|
buildMeta(builder)
|
||||||
}
|
}
|
||||||
applyStyle(name)
|
useStyle(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun configureSolid(obj: VisualObject3D, parent: GDMLVolume, solid: GDMLSolid) {
|
internal fun configureSolid(obj: VisualObject3D, parent: GDMLVolume, solid: GDMLSolid) {
|
||||||
@ -53,8 +55,8 @@ class GDMLTransformer(val root: GDML) {
|
|||||||
val styleName = "material[${material.name}]"
|
val styleName = "material[${material.name}]"
|
||||||
|
|
||||||
obj.useStyle(styleName) {
|
obj.useStyle(styleName) {
|
||||||
COLOR_KEY to Colors.rgbToString(random.nextInt(0, Int.MAX_VALUE))
|
MATERIAL_COLOR_KEY put random.nextInt(16777216)
|
||||||
"gdml.material" to material.name
|
"gdml.material" put material.name
|
||||||
}
|
}
|
||||||
|
|
||||||
obj.solidConfiguration(parent, solid)
|
obj.solidConfiguration(parent, solid)
|
||||||
@ -67,9 +69,11 @@ class GDMLTransformer(val root: GDML) {
|
|||||||
var onFinish: GDMLTransformer.() -> Unit = {}
|
var onFinish: GDMLTransformer.() -> Unit = {}
|
||||||
|
|
||||||
internal fun finalize(final: VisualGroup3D): VisualGroup3D {
|
internal fun finalize(final: VisualGroup3D): VisualGroup3D {
|
||||||
final.templates = templates
|
final.prototypes = proto
|
||||||
styleCache.forEach {
|
styleCache.forEach {
|
||||||
final.addStyle(it.key, it.value, false)
|
final.styleSheet {
|
||||||
|
define(it.key.toString(), it.value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
final.rotationOrder = RotationOrder.ZXY
|
final.rotationOrder = RotationOrder.ZXY
|
||||||
onFinish(this@GDMLTransformer)
|
onFinish(this@GDMLTransformer)
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
package hep.dataforge.vis.spatial.gdml
|
package hep.dataforge.vis.spatial.gdml
|
||||||
|
|
||||||
import hep.dataforge.names.EmptyName
|
|
||||||
|
import hep.dataforge.names.Name
|
||||||
import hep.dataforge.names.asName
|
import hep.dataforge.names.asName
|
||||||
import hep.dataforge.names.plus
|
import hep.dataforge.names.plus
|
||||||
import hep.dataforge.vis.common.get
|
import hep.dataforge.vis.common.get
|
||||||
import hep.dataforge.vis.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
|
||||||
@ -12,25 +15,28 @@ 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newRotation?.let {
|
||||||
|
val point = Point3D(it.x(), it.y(), it.z())
|
||||||
|
if (rotation != null || point != ZERO) {
|
||||||
|
rotation = point
|
||||||
}
|
}
|
||||||
rotation?.let {
|
|
||||||
this@withPosition.rotationX = rotation.x()
|
|
||||||
this@withPosition.rotationY = rotation.y()
|
|
||||||
this@withPosition.rotationZ = rotation.z()
|
|
||||||
//this@withPosition.rotationOrder = RotationOrder.ZXY
|
//this@withPosition.rotationOrder = RotationOrder.ZXY
|
||||||
}
|
}
|
||||||
scale?.let {
|
newScale?.let {
|
||||||
this@withPosition.scaleX = scale.x.toFloat()
|
val point = Point3D(it.x, it.y, it.z)
|
||||||
this@withPosition.scaleY = scale.y.toFloat()
|
if (scale != null || point != ONE) {
|
||||||
this@withPosition.scaleZ = scale.z.toFloat()
|
scale = point
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//TODO convert units if needed
|
//TODO convert units if needed
|
||||||
}
|
}
|
||||||
@ -161,8 +167,8 @@ private fun VisualGroup3D.addPhysicalVolume(
|
|||||||
}
|
}
|
||||||
GDMLTransformer.Action.CACHE -> {
|
GDMLTransformer.Action.CACHE -> {
|
||||||
val fullName = volumesName + volume.name.asName()
|
val fullName = volumesName + volume.name.asName()
|
||||||
if (context.templates[fullName] == null) {
|
if (context.proto[fullName] == null) {
|
||||||
context.templates[fullName] = volume(context, volume)
|
context.proto[fullName] = volume(context, volume)
|
||||||
}
|
}
|
||||||
|
|
||||||
this[physVolume.name ?: ""] = Proxy(fullName).apply {
|
this[physVolume.name ?: ""] = Proxy(fullName).apply {
|
||||||
@ -189,7 +195,7 @@ private fun VisualGroup3D.addDivisionVolume(
|
|||||||
|
|
||||||
//TODO add divisions
|
//TODO add divisions
|
||||||
set(
|
set(
|
||||||
EmptyName,
|
Name.EMPTY,
|
||||||
volume(
|
volume(
|
||||||
context,
|
context,
|
||||||
volume
|
volume
|
||||||
@ -215,8 +221,8 @@ private fun volume(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
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) {
|
||||||
context.configureSolid(this, group, solid)
|
context.configureSolid(this, group, solid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -245,3 +251,12 @@ fun GDML.toVisual(block: GDMLTransformer.() -> Unit = {}): VisualGroup3D {
|
|||||||
|
|
||||||
return context.finalize(volume(context, world))
|
return context.finalize(volume(context, world))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -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,40 +0,0 @@
|
|||||||
package hep.dataforge.vis.spatial.gdml
|
|
||||||
|
|
||||||
import hep.dataforge.vis.spatial.Material3D
|
|
||||||
import hep.dataforge.vis.spatial.Visual3DPlugin
|
|
||||||
import hep.dataforge.vis.spatial.VisualGroup3D
|
|
||||||
import hep.dataforge.vis.spatial.VisualObject3D
|
|
||||||
import hep.dataforge.vis.spatial.transform.RemoveSingleChild
|
|
||||||
import hep.dataforge.vis.spatial.transform.UnRef
|
|
||||||
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\\cubes.gdml")
|
|
||||||
|
|
||||||
val xmlReader = StAXReader(file.inputStream(), "UTF-8")
|
|
||||||
val xml = GDML.format.parse(GDML.serializer(), xmlReader)
|
|
||||||
val visual = xml.toVisual {
|
|
||||||
lUnit = LUnit.CM
|
|
||||||
|
|
||||||
solidConfiguration = { parent, solid ->
|
|
||||||
if (parent.physVolumes.isNotEmpty()) {
|
|
||||||
useStyle("opaque") {
|
|
||||||
Material3D.OPACITY_KEY to 0.3
|
|
||||||
VisualObject3D.LAYER_KEY to 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(visual as? VisualGroup3D)?.let { UnRef(it) }?.let { RemoveSingleChild(it) }
|
|
||||||
|
|
||||||
val string = Visual3DPlugin.json.stringify(VisualGroup3D.serializer(), visual)
|
|
||||||
|
|
||||||
val tmpFile = File.createTempFile("dataforge-visual", ".json")
|
|
||||||
|
|
||||||
tmpFile.writeText(string)
|
|
||||||
|
|
||||||
println(tmpFile.canonicalPath)
|
|
||||||
}
|
|
@ -1,62 +0,0 @@
|
|||||||
package hep.dataforge.vis.spatial.gdml
|
|
||||||
|
|
||||||
import hep.dataforge.vis.spatial.Material3D
|
|
||||||
import hep.dataforge.vis.spatial.Visual3DPlugin
|
|
||||||
import hep.dataforge.vis.spatial.VisualGroup3D
|
|
||||||
import hep.dataforge.vis.spatial.opacity
|
|
||||||
import hep.dataforge.vis.spatial.transform.RemoveSingleChild
|
|
||||||
import hep.dataforge.vis.spatial.transform.UnRef
|
|
||||||
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 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("ecal01lay") -> GDMLTransformer.Action.REJECT
|
|
||||||
else -> GDMLTransformer.Action.CACHE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
solidConfiguration = { parent, solid ->
|
|
||||||
if (parent.physVolumes.isNotEmpty()
|
|
||||||
|| solid.name.startsWith("Coil")
|
|
||||||
|| solid.name.startsWith("Yoke")
|
|
||||||
|| solid.name.startsWith("Magnet")
|
|
||||||
|| solid.name.startsWith("Pole")
|
|
||||||
) {
|
|
||||||
useStyle("opaque") {
|
|
||||||
Material3D.OPACITY_KEY to 0.3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// (visual as? VisualGroup3D)?.let { UnRef(it) }?.let { RemoveSingleChild(it) }
|
|
||||||
|
|
||||||
val string = Visual3DPlugin.json.stringify(VisualGroup3D.serializer(), visual)
|
|
||||||
|
|
||||||
val tmpFile = File.createTempFile("dataforge-visual", ".json")
|
|
||||||
|
|
||||||
tmpFile.writeText(string)
|
|
||||||
|
|
||||||
println(tmpFile.canonicalPath)
|
|
||||||
|
|
||||||
// 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)
|
||||||
|
}
|
||||||
|
|
@ -1,13 +1,12 @@
|
|||||||
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()
|
||||||
withSerialization()
|
|
||||||
}
|
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
jvm {
|
jvm {
|
||||||
@ -21,13 +20,18 @@ kotlin {
|
|||||||
}
|
}
|
||||||
jvmMain {
|
jvmMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":dataforge-vis-fx"))
|
implementation("org.fxyz3d:fxyz3d:0.5.2") {
|
||||||
implementation("org.fxyz3d:fxyz3d:0.5.2")
|
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(project(":wrappers"))
|
// 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"))
|
||||||
}
|
}
|
||||||
|
@ -2,16 +2,21 @@
|
|||||||
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
|
||||||
|
@SerialName("3d.box")
|
||||||
class Box(
|
class Box(
|
||||||
val xSize: Float,
|
val xSize: Float,
|
||||||
val ySize: Float,
|
val ySize: Float,
|
||||||
@ -27,9 +32,9 @@ 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 @@ 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"
|
||||||
|
|
||||||
|
@ -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
|
||||||
@ -33,13 +33,6 @@ class Composite(
|
|||||||
|
|
||||||
@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(
|
||||||
@ -51,13 +44,18 @@ inline fun VisualGroup3D.composite(
|
|||||||
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,6 +28,49 @@ 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(
|
||||||
|
@ -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,13 +18,6 @@ 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"
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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) }
|
@ -1,56 +1,96 @@
|
|||||||
package hep.dataforge.vis.spatial
|
package hep.dataforge.vis.spatial
|
||||||
|
|
||||||
|
import hep.dataforge.descriptors.NodeDescriptor
|
||||||
import hep.dataforge.meta.*
|
import hep.dataforge.meta.*
|
||||||
import hep.dataforge.names.asName
|
import hep.dataforge.names.asName
|
||||||
import hep.dataforge.names.plus
|
import hep.dataforge.names.plus
|
||||||
|
import hep.dataforge.values.ValueType
|
||||||
import hep.dataforge.vis.common.Colors
|
import hep.dataforge.vis.common.Colors
|
||||||
import hep.dataforge.vis.common.VisualObject
|
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_COLOR_KEY
|
||||||
import hep.dataforge.vis.spatial.Material3D.Companion.COLOR_KEY
|
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_OPACITY_KEY
|
||||||
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_KEY
|
|
||||||
import hep.dataforge.vis.spatial.Material3D.Companion.OPACITY_KEY
|
|
||||||
|
|
||||||
class Material3D(override val config: Config) : Specific {
|
class Material3D(override val config: Config) : Specific {
|
||||||
|
|
||||||
val color by string()
|
var color by string(key = COLOR_KEY)
|
||||||
|
|
||||||
val opacity by float(1f)
|
var specularColor by string()
|
||||||
|
|
||||||
|
var opacity by float(1f, key = OPACITY_KEY)
|
||||||
|
|
||||||
|
var wireframe by boolean(false, WIREFRAME_KEY)
|
||||||
|
|
||||||
companion object : Specification<Material3D> {
|
companion object : Specification<Material3D> {
|
||||||
override fun wrap(config: Config): Material3D = Material3D(config)
|
override fun wrap(config: Config): Material3D = Material3D(config)
|
||||||
|
|
||||||
val MATERIAL_KEY = "material".asName()
|
val MATERIAL_KEY = "material".asName()
|
||||||
val COLOR_KEY = MATERIAL_KEY + "color"
|
internal val COLOR_KEY = "color".asName()
|
||||||
val OPACITY_KEY = MATERIAL_KEY + "opacity"
|
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 VisualObject.color(rgb: String) {
|
fun VisualObject3D.color(rgb: String) {
|
||||||
setProperty(COLOR_KEY, rgb)
|
setProperty(MATERIAL_COLOR_KEY, rgb)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun VisualObject.color(rgb: Int) = color(Colors.rgbToString(rgb))
|
fun VisualObject3D.color(rgb: Int) {
|
||||||
|
setProperty(MATERIAL_COLOR_KEY, rgb)
|
||||||
|
}
|
||||||
|
|
||||||
fun VisualObject.color(r: UByte, g: UByte, b: UByte) = color( Colors.rgbToString(r,g,b))
|
fun VisualObject3D.color(r: UByte, g: UByte, b: UByte) = setProperty(
|
||||||
|
MATERIAL_COLOR_KEY,
|
||||||
|
Colors.rgbToMeta(r, g, b)
|
||||||
|
)
|
||||||
|
|
||||||
var VisualObject.color: String?
|
/**
|
||||||
get() = getProperty(COLOR_KEY).string
|
* 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) {
|
set(value) {
|
||||||
if (value != null) {
|
setProperty(MATERIAL_COLOR_KEY, value)
|
||||||
color(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 VisualObject.material: Material3D?
|
var VisualObject3D.opacity: Double?
|
||||||
get() = getProperty(MATERIAL_KEY).node?.let { Material3D.wrap(it) }
|
get() = getProperty(MATERIAL_OPACITY_KEY).double
|
||||||
set(value) = setProperty(MATERIAL_KEY, value?.config)
|
|
||||||
|
|
||||||
fun VisualObject.material(builder: Material3D.() -> Unit) {
|
|
||||||
material = Material3D.build(builder)
|
|
||||||
}
|
|
||||||
|
|
||||||
var VisualObject.opacity: Double?
|
|
||||||
get() = getProperty(OPACITY_KEY).double
|
|
||||||
set(value) {
|
set(value) {
|
||||||
setProperty(OPACITY_KEY, value)
|
setProperty(MATERIAL_OPACITY_KEY, value)
|
||||||
}
|
}
|
@ -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 hep.dataforge.vis.common.number
|
import hep.dataforge.vis.common.number
|
||||||
@ -20,6 +20,7 @@ class PolyLine(var points: List<Point3D>) : AbstractVisualObject(), VisualObject
|
|||||||
|
|
||||||
//var lineType by string()
|
//var lineType by string()
|
||||||
var thickness by number(1.0, key = "material.thickness")
|
var thickness by number(1.0, key = "material.thickness")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun VisualGroup3D.polyline(vararg points: Point3D, name: String = "", action: PolyLine.() -> Unit = {}) =
|
fun VisualGroup3D.polyline(vararg points: Point3D, name: String = "", action: PolyLine.() -> Unit = {}) =
|
||||||
|
@ -2,18 +2,20 @@
|
|||||||
|
|
||||||
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.*
|
import hep.dataforge.meta.Config
|
||||||
|
import hep.dataforge.meta.Laminate
|
||||||
|
import hep.dataforge.meta.MetaItem
|
||||||
|
import hep.dataforge.meta.get
|
||||||
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.plus
|
import hep.dataforge.names.plus
|
||||||
import hep.dataforge.vis.common.AbstractVisualObject
|
import hep.dataforge.vis.common.*
|
||||||
import hep.dataforge.vis.common.MutableVisualGroup
|
import kotlinx.serialization.SerialName
|
||||||
import hep.dataforge.vis.common.VisualGroup
|
|
||||||
import hep.dataforge.vis.common.VisualObject
|
|
||||||
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.component1
|
||||||
import kotlin.collections.component2
|
import kotlin.collections.component2
|
||||||
@ -23,6 +25,7 @@ import kotlin.collections.set
|
|||||||
* A proxy [VisualObject3D] to reuse a template object
|
* A proxy [VisualObject3D] to reuse a template object
|
||||||
*/
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@SerialName("3d.proxy")
|
||||||
class Proxy(val templateName: Name) : AbstractVisualObject(), VisualGroup, VisualObject3D {
|
class Proxy(val templateName: Name) : AbstractVisualObject(), VisualGroup, VisualObject3D {
|
||||||
|
|
||||||
override var position: Point3D? = null
|
override var position: Point3D? = null
|
||||||
@ -36,22 +39,18 @@ class Proxy(val templateName: Name) : AbstractVisualObject(), VisualGroup, Visua
|
|||||||
* Recursively search for defined template in the parent
|
* Recursively search for defined template in the parent
|
||||||
*/
|
*/
|
||||||
val prototype: 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 fun getStyle(name: Name): Meta? = (parent as VisualGroup?)?.getStyle(name)
|
override val styleSheet: StyleSheet
|
||||||
|
get() = (parent as? VisualGroup)?.styleSheet ?: StyleSheet(this)
|
||||||
override fun addStyle(name: Name, meta: Meta, apply: Boolean) {
|
|
||||||
(parent as VisualGroup?)?.addStyle(name, meta, apply)
|
|
||||||
//do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? {
|
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? {
|
||||||
return if (inherit) {
|
return if (inherit) {
|
||||||
properties?.get(name)
|
properties?.get(name)
|
||||||
?: mergedStyles[name]
|
?: mergedStyles[name]
|
||||||
?: prototype.getProperty(name, false)
|
?: prototype.getProperty(name)
|
||||||
?: parent?.getProperty(name, inherit)
|
?: parent?.getProperty(name)
|
||||||
} else {
|
} else {
|
||||||
properties?.get(name)
|
properties?.get(name)
|
||||||
?: mergedStyles[name]
|
?: mergedStyles[name]
|
||||||
@ -59,41 +58,35 @@ class Proxy(val templateName: Name) : AbstractVisualObject(), VisualGroup, Visua
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun MetaBuilder.updateMeta() {
|
|
||||||
//TODO add reference to child
|
|
||||||
updatePosition()
|
|
||||||
}
|
|
||||||
|
|
||||||
override val children: Map<NameToken, ProxyChild>
|
override val children: Map<NameToken, ProxyChild>
|
||||||
get() = (prototype as? MutableVisualGroup)?.children?.mapValues {
|
get() = (prototype as? MutableVisualGroup)?.children
|
||||||
|
?.filter { !it.key.toString().startsWith("@") }
|
||||||
|
?.mapValues {
|
||||||
ProxyChild(it.key.asName())
|
ProxyChild(it.key.asName())
|
||||||
} ?: emptyMap()
|
} ?: emptyMap()
|
||||||
|
|
||||||
|
@Transient
|
||||||
private val propertyCache: HashMap<Name, Config> = HashMap()
|
private val propertyCache: HashMap<Name, Config> = HashMap()
|
||||||
|
|
||||||
fun childPropertyName(childName: Name, propertyName: Name): Name {
|
fun childPropertyName(childName: Name, propertyName: Name): Name {
|
||||||
return NameToken(PROXY_CHILD_PROPERTY_PREFIX, childName.toString()) + propertyName
|
return NameToken(PROXY_CHILD_PROPERTY_PREFIX, childName.toString()) + propertyName
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun prototypeFor(name: Name): VisualObject =
|
private fun prototypeFor(name: Name): VisualObject {
|
||||||
(prototype as? VisualGroup)?.get(name)
|
return (prototype as? VisualGroup)?.get(name)
|
||||||
?: error("Prototype with name $name not found in ${this@Proxy}")
|
?: error("Prototype with name $name not found in $this")
|
||||||
|
|
||||||
|
|
||||||
override var styles: List<Name>
|
|
||||||
get() = super.styles + prototype.styles
|
|
||||||
set(value) {
|
|
||||||
setProperty(VisualObject.STYLE_KEY, value.map { it.toString() })
|
|
||||||
styleChanged()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun allProperties(): Laminate = Laminate(properties, mergedStyles, prototype.allProperties())
|
||||||
|
|
||||||
//override fun findAllStyles(): Laminate = Laminate((styles + prototype.styles).mapNotNull { findStyle(it) })
|
//override fun findAllStyles(): Laminate = Laminate((styles + prototype.styles).mapNotNull { findStyle(it) })
|
||||||
|
|
||||||
|
@Serializable
|
||||||
inner class ProxyChild(val name: Name) : AbstractVisualObject(), VisualGroup {
|
inner class ProxyChild(val name: Name) : AbstractVisualObject(), VisualGroup {
|
||||||
|
|
||||||
val prototype: VisualObject by lazy {
|
val prototype: VisualObject get() = prototypeFor(name)
|
||||||
prototypeFor(name)
|
|
||||||
}
|
override val styleSheet: StyleSheet get() = this@Proxy.styleSheet
|
||||||
|
|
||||||
override val children: Map<NameToken, VisualObject>
|
override val children: Map<NameToken, VisualObject>
|
||||||
get() = (prototype as? VisualGroup)?.children?.mapValues { (key, _) ->
|
get() = (prototype as? VisualGroup)?.children?.mapValues { (key, _) ->
|
||||||
@ -102,12 +95,6 @@ class Proxy(val templateName: Name) : AbstractVisualObject(), VisualGroup, Visua
|
|||||||
)
|
)
|
||||||
} ?: emptyMap()
|
} ?: emptyMap()
|
||||||
|
|
||||||
override fun getStyle(name: Name): Meta? = this@Proxy.getStyle(name)
|
|
||||||
|
|
||||||
override fun addStyle(name: Name, meta: Meta, apply: Boolean) {
|
|
||||||
this@Proxy.addStyle(name, meta, apply)
|
|
||||||
}
|
|
||||||
|
|
||||||
override var properties: Config?
|
override var properties: Config?
|
||||||
get() = propertyCache[name]
|
get() = propertyCache[name]
|
||||||
set(value) {
|
set(value) {
|
||||||
@ -129,15 +116,18 @@ class Proxy(val templateName: Name) : AbstractVisualObject(), VisualGroup, Visua
|
|||||||
return if (inherit) {
|
return if (inherit) {
|
||||||
properties?.get(name)
|
properties?.get(name)
|
||||||
?: mergedStyles[name]
|
?: mergedStyles[name]
|
||||||
?: prototype.getProperty(name, inherit)
|
?: prototype.getProperty(name)
|
||||||
?: parent?.getProperty(name, inherit)
|
?: parent?.getProperty(name)
|
||||||
} else {
|
} else {
|
||||||
properties?.get(name)
|
properties?.get(name)
|
||||||
?: mergedStyles[name]
|
?: mergedStyles[name]
|
||||||
?: prototype.getProperty(name, inherit)
|
?: prototype.getProperty(name, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun allProperties(): Laminate = Laminate(properties, mergedStyles, prototype.allProperties())
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -145,15 +135,36 @@ class Proxy(val templateName: Name) : AbstractVisualObject(), VisualGroup, Visua
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val VisualObject.prototype: VisualObject?
|
val VisualObject.prototype: VisualObject
|
||||||
get() = when (this) {
|
get() = when (this) {
|
||||||
is Proxy -> prototype
|
is Proxy -> prototype
|
||||||
is Proxy.ProxyChild -> prototype
|
is Proxy.ProxyChild -> prototype
|
||||||
else -> null
|
else -> this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create ref for existing prototype
|
||||||
|
*/
|
||||||
inline fun VisualGroup3D.ref(
|
inline fun VisualGroup3D.ref(
|
||||||
templateName: Name,
|
templateName: Name,
|
||||||
name: String = "",
|
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,6 +24,19 @@ 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(
|
||||||
|
@ -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(
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
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.ConfigSerializer
|
import hep.dataforge.io.serialization.ConfigSerializer
|
||||||
import hep.dataforge.io.MetaSerializer
|
import hep.dataforge.io.serialization.MetaSerializer
|
||||||
import hep.dataforge.io.NameSerializer
|
import hep.dataforge.io.serialization.NameSerializer
|
||||||
import hep.dataforge.meta.*
|
import hep.dataforge.meta.*
|
||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.Name
|
||||||
import hep.dataforge.vis.common.VisualObject
|
import hep.dataforge.vis.common.VisualObject
|
||||||
@ -30,14 +31,14 @@ 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 {
|
||||||
contextual(Point3DSerializer)
|
contextual(Point3DSerializer)
|
||||||
contextual(Point2DSerializer)
|
contextual(Point2DSerializer)
|
||||||
contextual(NameSerializer)
|
contextual(NameSerializer)
|
||||||
contextual(NameTokenSerializer)
|
contextual(NameTokenSerializer)
|
||||||
contextual(Meta::class, MetaSerializer)
|
contextual(MetaSerializer)
|
||||||
contextual(ConfigSerializer)
|
contextual(ConfigSerializer)
|
||||||
|
|
||||||
polymorphic(VisualObject::class, VisualObject3D::class) {
|
polymorphic(VisualObject::class, VisualObject3D::class) {
|
||||||
@ -47,7 +48,8 @@ class Visual3DPlugin(meta: Meta) : AbstractPlugin(meta) {
|
|||||||
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()
|
||||||
addSubclass(Extruded.serializer())
|
Extruded::class with Extruded.serializer()
|
||||||
|
addSubclass(Label3D.serializer())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,19 +8,16 @@
|
|||||||
|
|
||||||
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.MetaSerializer
|
import hep.dataforge.io.serialization.MetaSerializer
|
||||||
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.Meta
|
|
||||||
import hep.dataforge.meta.MetaBuilder
|
|
||||||
import hep.dataforge.meta.set
|
|
||||||
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.isEmpty
|
import hep.dataforge.names.isEmpty
|
||||||
import hep.dataforge.vis.common.AbstractVisualGroup
|
import hep.dataforge.vis.common.AbstractVisualGroup
|
||||||
import hep.dataforge.vis.common.VisualGroup
|
import hep.dataforge.vis.common.StyleSheet
|
||||||
import hep.dataforge.vis.common.VisualObject
|
import hep.dataforge.vis.common.VisualObject
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
@ -31,19 +28,23 @@ import kotlin.collections.set
|
|||||||
* Represents 3-dimensional Visual Group
|
* Represents 3-dimensional Visual Group
|
||||||
*/
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@SerialName("group.3d")
|
||||||
class VisualGroup3D : AbstractVisualGroup(), VisualObject3D {
|
class VisualGroup3D : AbstractVisualGroup(), VisualObject3D {
|
||||||
/**
|
/**
|
||||||
* A container for templates visible inside this group
|
* A container for templates visible inside this group
|
||||||
*/
|
*/
|
||||||
var templates: VisualGroup3D? = null
|
@SerialName(PROTOTYPES_KEY)
|
||||||
|
var prototypes: VisualGroup3D? = null
|
||||||
set(value) {
|
set(value) {
|
||||||
value?.parent = this
|
value?.parent = this
|
||||||
field = value
|
field = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override var styleSheet: StyleSheet? = null
|
||||||
|
private set
|
||||||
|
|
||||||
//FIXME to be lifted to AbstractVisualGroup after https://github.com/Kotlin/kotlinx.serialization/issues/378 is fixed
|
//FIXME to be lifted to AbstractVisualGroup after https://github.com/Kotlin/kotlinx.serialization/issues/378 is fixed
|
||||||
override var properties: Config? = null
|
override var properties: Config? = null
|
||||||
override val styleSheet = HashMap<Name, Meta>()
|
|
||||||
|
|
||||||
override var position: Point3D? = null
|
override var position: Point3D? = null
|
||||||
override var rotation: Point3D? = null
|
override var rotation: Point3D? = null
|
||||||
@ -53,6 +54,19 @@ class VisualGroup3D : AbstractVisualGroup(), VisualObject3D {
|
|||||||
private val _children = HashMap<NameToken, VisualObject>()
|
private val _children = HashMap<NameToken, VisualObject>()
|
||||||
override val children: Map<NameToken, VisualObject> get() = _children
|
override val children: Map<NameToken, VisualObject> get() = _children
|
||||||
|
|
||||||
|
init {
|
||||||
|
//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) {
|
override fun removeChild(token: NameToken) {
|
||||||
_children.remove(token)
|
_children.remove(token)
|
||||||
childrenChanged(token.asName(), null)
|
childrenChanged(token.asName(), null)
|
||||||
@ -88,36 +102,42 @@ class VisualGroup3D : AbstractVisualGroup(), VisualObject3D {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getTemplate(name: Name): VisualObject3D? =
|
override fun attachChildren() {
|
||||||
templates?.get(name) as? VisualObject3D
|
super.attachChildren()
|
||||||
?: (parent as? VisualGroup3D)?.getTemplate(name)
|
prototypes?.run {
|
||||||
|
parent = this
|
||||||
override fun MetaBuilder.updateMeta() {
|
attachChildren()
|
||||||
set(TEMPLATES_KEY, templates?.toMeta())
|
}
|
||||||
updatePosition()
|
|
||||||
updateChildren()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val TEMPLATES_KEY = "templates"
|
const val PROTOTYPES_KEY = "templates"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A fix for serialization bug that writes all proper parents inside the tree after deserialization
|
* Ger a prototype redirecting the request to the parent if prototype is not found
|
||||||
*/
|
*/
|
||||||
fun VisualGroup.attachChildren() {
|
fun VisualGroup3D.getPrototype(name: Name): VisualObject3D? =
|
||||||
this.children.values.forEach {
|
prototypes?.get(name) as? VisualObject3D ?: (parent as? VisualGroup3D)?.getPrototype(name)
|
||||||
it.parent = this
|
|
||||||
(it as? VisualGroup)?.attachChildren()
|
/**
|
||||||
}
|
* Defined a prototype inside current group
|
||||||
if (this is VisualGroup3D) {
|
*/
|
||||||
templates?.also {
|
fun VisualGroup3D.setPrototype(name: Name, obj: VisualObject3D) {
|
||||||
it.parent = this
|
(prototypes ?: VisualGroup3D().also { this.prototypes = it })[name] = obj
|
||||||
it.attachChildren()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a group with given [key], attach it to this parent and return it.
|
||||||
|
*/
|
||||||
fun VisualGroup3D.group(key: String = "", action: VisualGroup3D.() -> Unit = {}): VisualGroup3D =
|
fun VisualGroup3D.group(key: String = "", action: VisualGroup3D.() -> Unit = {}): VisualGroup3D =
|
||||||
VisualGroup3D().apply(action).also { set(key, it) }
|
VisualGroup3D().apply(action).also { set(key, it) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create or edit prototype node as a group
|
||||||
|
*/
|
||||||
|
inline fun VisualGroup3D.prototypes(builder: VisualGroup3D.() -> Unit): Unit {
|
||||||
|
(prototypes ?: VisualGroup3D().also { this.prototypes = it }).run(builder)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -2,15 +2,15 @@
|
|||||||
|
|
||||||
package hep.dataforge.vis.spatial
|
package hep.dataforge.vis.spatial
|
||||||
|
|
||||||
import hep.dataforge.io.NameSerializer
|
import hep.dataforge.io.serialization.NameSerializer
|
||||||
import hep.dataforge.meta.*
|
import hep.dataforge.meta.*
|
||||||
import hep.dataforge.names.asName
|
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.VisualObject
|
import hep.dataforge.vis.common.VisualObject
|
||||||
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.LAYER_KEY
|
|
||||||
import hep.dataforge.vis.spatial.VisualObject3D.Companion.IGNORE_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.SELECTED_KEY
|
import hep.dataforge.vis.spatial.VisualObject3D.Companion.SELECTED_KEY
|
||||||
import hep.dataforge.vis.spatial.VisualObject3D.Companion.VISIBLE_KEY
|
import hep.dataforge.vis.spatial.VisualObject3D.Companion.VISIBLE_KEY
|
||||||
import kotlinx.serialization.UseSerializers
|
import kotlinx.serialization.UseSerializers
|
||||||
@ -43,7 +43,7 @@ interface VisualObject3D : VisualObject {
|
|||||||
val LAYER_KEY = "layer".asName()
|
val LAYER_KEY = "layer".asName()
|
||||||
val IGNORE_KEY = "ignore".asName()
|
val IGNORE_KEY = "ignore".asName()
|
||||||
|
|
||||||
val GEOMETRY_KEY = "geometey".asName()
|
val GEOMETRY_KEY = "geometry".asName()
|
||||||
|
|
||||||
val x = "x".asName()
|
val x = "x".asName()
|
||||||
val y = "y".asName()
|
val y = "y".asName()
|
||||||
@ -80,7 +80,7 @@ var VisualObject3D.layer: Int
|
|||||||
setProperty(LAYER_KEY, value)
|
setProperty(LAYER_KEY, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Output<VisualObject3D>.render(meta: Meta = EmptyMeta, action: VisualGroup3D.() -> Unit) =
|
fun Renderer<VisualObject3D>.render(meta: Meta = EmptyMeta, action: VisualGroup3D.() -> Unit) =
|
||||||
render(VisualGroup3D().apply(action), meta)
|
render(VisualGroup3D().apply(action), meta)
|
||||||
|
|
||||||
// Common properties
|
// Common properties
|
||||||
@ -132,21 +132,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 =
|
||||||
@ -156,21 +156,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 =
|
||||||
@ -180,19 +180,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,23 +26,7 @@ expect class Point3D(x: Number, y: Number, z: Number) {
|
|||||||
var z: Double
|
var z: Double
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun Point3D?.plus(other: Point3D?): Point3D? {
|
expect operator fun Point3D.plus(other: Point3D): Point3D
|
||||||
return when {
|
|
||||||
this == null && other == null -> null
|
|
||||||
this == null -> other
|
|
||||||
other == null -> this
|
|
||||||
else -> Point3D(x + other.x, y + other.y, z + other.z)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
operator fun Point3D?.minus(other: Point3D?): Point3D? {
|
|
||||||
return when {
|
|
||||||
this == null && other == null -> null
|
|
||||||
this == null -> Point3D(-other!!.x, -other.y, -other.z)
|
|
||||||
other == null -> this
|
|
||||||
else -> Point3D(x - other.x, y - other.y, z - other.z)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
operator fun Point3D.component1() = x
|
operator fun Point3D.component1() = x
|
||||||
operator fun Point3D.component2() = y
|
operator fun Point3D.component2() = y
|
||||||
@ -50,10 +34,8 @@ 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,45 +1,97 @@
|
|||||||
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.NameToken
|
||||||
import hep.dataforge.names.toName
|
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.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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -10,11 +10,13 @@ import hep.dataforge.vis.spatial.*
|
|||||||
internal fun mergeChild(parent: VisualGroup, child: VisualObject): VisualObject {
|
internal fun mergeChild(parent: VisualGroup, child: VisualObject): VisualObject {
|
||||||
return child.apply {
|
return child.apply {
|
||||||
|
|
||||||
parent.properties?.let { config.update(it) }
|
config.update(parent.config)
|
||||||
|
|
||||||
|
//parent.properties?.let { config.update(it) }
|
||||||
|
|
||||||
if (this is VisualObject3D && parent is VisualObject3D) {
|
if (this is VisualObject3D && parent is VisualObject3D) {
|
||||||
position += parent.position
|
position = (position ?: World.ZERO) + (parent.position ?: World.ZERO)
|
||||||
rotation += parent.rotation
|
rotation = (parent.rotation ?: World.ZERO) + (parent.rotation ?: World.ZERO)
|
||||||
scale = when {
|
scale = when {
|
||||||
scale == null && parent.scale == null -> null
|
scale == null && parent.scale == null -> null
|
||||||
scale == null -> parent.scale
|
scale == null -> parent.scale
|
||||||
@ -49,7 +51,7 @@ object RemoveSingleChild : VisualTreeTransform<VisualGroup3D>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
replaceChildren()
|
replaceChildren()
|
||||||
templates?.replaceChildren()
|
prototypes?.replaceChildren()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun VisualGroup3D.clone(): VisualGroup3D {
|
override fun VisualGroup3D.clone(): VisualGroup3D {
|
||||||
|
@ -24,7 +24,7 @@ object UnRef : VisualTreeTransform<VisualGroup3D>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun MutableVisualGroup.unref(name: Name) {
|
private fun MutableVisualGroup.unref(name: Name) {
|
||||||
(this as? VisualGroup3D)?.templates?.set(name, null)
|
(this as? VisualGroup3D)?.prototypes?.set(name, null)
|
||||||
children.filter { (it.value as? Proxy)?.templateName == name }.forEach { (key, value) ->
|
children.filter { (it.value as? Proxy)?.templateName == name }.forEach { (key, value) ->
|
||||||
val proxy = value as Proxy
|
val proxy = value as Proxy
|
||||||
val newChild = mergeChild(proxy, proxy.prototype)
|
val newChild = mergeChild(proxy, proxy.prototype)
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
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
|
||||||
|
|
||||||
@ -26,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)
|
||||||
}
|
}
|
||||||
|
@ -18,8 +18,8 @@ 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("intersect") {
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -11,10 +11,12 @@ class SerializationTest {
|
|||||||
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 string = json.stringify(Box.serializer(), cube)
|
val string = json.stringify(Box.serializer(), cube)
|
||||||
println(string)
|
println(string)
|
||||||
val newCube = json.parse(Box.serializer(), string)
|
val newCube = json.parse(Box.serializer(), string)
|
||||||
assertEquals(cube.toMeta(),newCube.toMeta())
|
assertEquals(cube.config, newCube.config)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,137 +0,0 @@
|
|||||||
@file:Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE")
|
|
||||||
|
|
||||||
package hep.dataforge.vis.spatial.editor
|
|
||||||
|
|
||||||
import hep.dataforge.meta.string
|
|
||||||
import hep.dataforge.names.EmptyName
|
|
||||||
import hep.dataforge.names.Name
|
|
||||||
import hep.dataforge.names.NameToken
|
|
||||||
import hep.dataforge.vis.common.VisualGroup
|
|
||||||
import hep.dataforge.vis.common.VisualObject
|
|
||||||
import hep.dataforge.vis.common.getProperty
|
|
||||||
import hep.dataforge.vis.jsObject
|
|
||||||
import hep.dataforge.vis.spatial.Proxy
|
|
||||||
import hep.dataforge.vis.spatial.visible
|
|
||||||
import info.laht.threekt.loaders.Cache.clear
|
|
||||||
import kotlinx.html.div
|
|
||||||
import kotlinx.html.dom.append
|
|
||||||
import org.w3c.dom.Element
|
|
||||||
import kotlin.dom.clear
|
|
||||||
import kotlin.js.json
|
|
||||||
|
|
||||||
operator fun Name.plus(other: NameToken): Name = Name(tokens + other)
|
|
||||||
|
|
||||||
|
|
||||||
private fun createInspireTree(block: Config.() -> Unit = {}): InspireTree {
|
|
||||||
val config = (json(
|
|
||||||
"checkbox" to json(
|
|
||||||
"autoCheckChildren" to false
|
|
||||||
)
|
|
||||||
) as Config).apply(block)
|
|
||||||
return InspireTree(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun VisualObject.toTree(onFocus: (VisualObject?, String?) -> Unit = { _, _ -> }): InspireTree {
|
|
||||||
|
|
||||||
val map = HashMap<String, VisualObject>()
|
|
||||||
|
|
||||||
fun generateNodeConfig(item: VisualObject, fullName: Name): NodeConfig {
|
|
||||||
val title = item.getProperty("title").string ?: fullName.last()?.toString() ?: "root"
|
|
||||||
val className = if (item is Proxy) {
|
|
||||||
item.prototype::class.toString()
|
|
||||||
} else {
|
|
||||||
item::class.toString()
|
|
||||||
}.replace("class ", "")
|
|
||||||
|
|
||||||
val text: String = if (title.startsWith("@")) {
|
|
||||||
"[$className}]"
|
|
||||||
} else {
|
|
||||||
"$title[$className}]"
|
|
||||||
}
|
|
||||||
|
|
||||||
return json(
|
|
||||||
"children" to if ((item as? VisualGroup)?.children?.isEmpty() != false) {
|
|
||||||
emptyArray<NodeConfig>()
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
},
|
|
||||||
"text" to text,
|
|
||||||
"id" to fullName.toString(),
|
|
||||||
"itree" to json(
|
|
||||||
"state" to json(
|
|
||||||
"checked" to (item.visible ?: true)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
) as NodeConfig
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fun TreeNode.fillChildren(group: VisualObject, groupName: Name) {
|
|
||||||
if(group is VisualGroup) {
|
|
||||||
group.children.forEach { (token, obj) ->
|
|
||||||
if (!token.body.startsWith("@")) {
|
|
||||||
val name = groupName + token
|
|
||||||
val nodeConfig = generateNodeConfig(obj, name)
|
|
||||||
val childNode = addChild(nodeConfig)
|
|
||||||
map[childNode.id] = obj
|
|
||||||
if (obj is VisualGroup) {
|
|
||||||
childNode.fillChildren(obj, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val inspireTree = createInspireTree {
|
|
||||||
|
|
||||||
}
|
|
||||||
val nodeConfig = generateNodeConfig(this, EmptyName)
|
|
||||||
val rootNode = inspireTree.addNode(nodeConfig)
|
|
||||||
map[rootNode.id] = this
|
|
||||||
rootNode.fillChildren(this, EmptyName)
|
|
||||||
|
|
||||||
// inspireTree.on("node.selected") { node: TreeNode, isLoadEvent: Boolean ->
|
|
||||||
// if (!isLoadEvent) {
|
|
||||||
// map[node.id]?.selected = node.selected()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// inspireTree.on("node.deselect") { node: TreeNode ->
|
|
||||||
// map[node.id]?.selected = node.selected()
|
|
||||||
// }
|
|
||||||
|
|
||||||
inspireTree.on("node.checked") { node: TreeNode, isLoadEvent: Boolean ->
|
|
||||||
if (!isLoadEvent) {
|
|
||||||
map[node.id]?.visible = node.checked()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inspireTree.on("node.unchecked") { node: TreeNode ->
|
|
||||||
if (!node.indeterminate()) {
|
|
||||||
map[node.id]?.visible = node.checked()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inspireTree.on("node.focused") { node: TreeNode, isLoadEvent: Boolean ->
|
|
||||||
if (!isLoadEvent) {
|
|
||||||
onFocus(map[node.id], node.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inspireTree.collapseDeep()
|
|
||||||
|
|
||||||
return inspireTree
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Element.visualObjectTree(group: VisualObject, onFocus: (VisualObject?, String?) -> Unit) {
|
|
||||||
this.clear()
|
|
||||||
append {
|
|
||||||
card("Visual object tree") {
|
|
||||||
val domConfig = jsObject<DomConfig> {
|
|
||||||
target = div()
|
|
||||||
showCheckboxes = false
|
|
||||||
}
|
|
||||||
InspireTreeDOM(group.toTree(onFocus), domConfig)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
package hep.dataforge.vis.spatial.editor
|
|
||||||
|
|
||||||
import hep.dataforge.vis.spatial.three.ThreeOutput
|
|
||||||
import kotlinx.html.InputType
|
|
||||||
import kotlinx.html.dom.append
|
|
||||||
import kotlinx.html.js.div
|
|
||||||
import kotlinx.html.js.input
|
|
||||||
import kotlinx.html.js.label
|
|
||||||
import org.w3c.dom.Element
|
|
||||||
import kotlin.dom.clear
|
|
||||||
|
|
||||||
fun Element.threeOutputConfig(output: ThreeOutput) {
|
|
||||||
clear()
|
|
||||||
append {
|
|
||||||
card("Layers"){
|
|
||||||
div("row") {
|
|
||||||
(0..11).forEach { layer ->
|
|
||||||
div("col-1") {
|
|
||||||
label { +layer.toString() }
|
|
||||||
input(type = InputType.checkBox).apply {
|
|
||||||
if (layer == 0) {
|
|
||||||
checked = true
|
|
||||||
}
|
|
||||||
onchange = {
|
|
||||||
if (checked) {
|
|
||||||
output.camera.layers.enable(layer)
|
|
||||||
} else {
|
|
||||||
output.camera.layers.disable(layer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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,96 +0,0 @@
|
|||||||
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 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 Materials {
|
|
||||||
val DEFAULT_COLOR = Color(Colors.darkgreen)
|
|
||||||
val DEFAULT = MeshPhongMaterial().apply {
|
|
||||||
color.set(DEFAULT_COLOR)
|
|
||||||
}
|
|
||||||
val DEFAULT_LINE_COLOR = Color(Colors.black)
|
|
||||||
val DEFAULT_LINE = LineBasicMaterial().apply {
|
|
||||||
color.set(DEFAULT_LINE_COLOR)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private val materialCache = HashMap<Meta, Material>()
|
|
||||||
private val lineMaterialCache = HashMap<Meta, Material>()
|
|
||||||
|
|
||||||
fun getMaterial(meta: Meta): Material = materialCache.getOrPut(meta) {
|
|
||||||
MeshBasicMaterial().apply {
|
|
||||||
color = meta["color"]?.color() ?: DEFAULT_COLOR
|
|
||||||
opacity = meta["opacity"]?.double ?: 1.0
|
|
||||||
transparent = meta["transparent"].boolean ?: (opacity < 1.0)
|
|
||||||
//node["specularColor"]?.let { specular = it.color() }
|
|
||||||
//side = 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getLineMaterial(meta: Meta): Material = lineMaterialCache.getOrPut(meta) {
|
|
||||||
LineBasicMaterial().apply {
|
|
||||||
color = meta["color"]?.color() ?: DEFAULT_LINE_COLOR
|
|
||||||
opacity = meta["opacity"].double ?: 1.0
|
|
||||||
transparent = meta["transparent"].boolean ?: (opacity < 1.0)
|
|
||||||
linewidth = meta["thickness"].double ?: 1.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Infer Three material based on meta item
|
|
||||||
*/
|
|
||||||
fun Meta?.jsMaterial(): Material {
|
|
||||||
return if (this == null) {
|
|
||||||
Materials.DEFAULT
|
|
||||||
} else {
|
|
||||||
Materials.getMaterial(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Meta?.jsLineMaterial(): Material {
|
|
||||||
return if (this == null) {
|
|
||||||
Materials.DEFAULT_LINE
|
|
||||||
} else{
|
|
||||||
Materials.getLineMaterial(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun Material3D?.jsMaterial(): Material = this?.config.jsMaterial()
|
|
||||||
fun Material3D?.jsLineMaterial(): Material = this?.config.jsLineMaterial()
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
|||||||
package hep.dataforge.vis.spatial.three
|
package hep.dataforge.vis.spatial.three
|
||||||
|
|
||||||
|
import hep.dataforge.meta.Meta
|
||||||
import hep.dataforge.meta.boolean
|
import hep.dataforge.meta.boolean
|
||||||
|
import hep.dataforge.meta.get
|
||||||
import hep.dataforge.meta.node
|
import hep.dataforge.meta.node
|
||||||
import hep.dataforge.names.asName
|
import hep.dataforge.names.asName
|
||||||
import hep.dataforge.names.plus
|
import hep.dataforge.names.plus
|
||||||
@ -8,10 +10,10 @@ import hep.dataforge.names.startsWith
|
|||||||
import hep.dataforge.vis.spatial.Material3D
|
import hep.dataforge.vis.spatial.Material3D
|
||||||
import hep.dataforge.vis.spatial.VisualObject3D
|
import hep.dataforge.vis.spatial.VisualObject3D
|
||||||
import hep.dataforge.vis.spatial.layer
|
import hep.dataforge.vis.spatial.layer
|
||||||
import hep.dataforge.vis.spatial.material
|
|
||||||
import info.laht.threekt.core.BufferGeometry
|
import info.laht.threekt.core.BufferGeometry
|
||||||
import info.laht.threekt.geometries.EdgesGeometry
|
import info.laht.threekt.geometries.EdgesGeometry
|
||||||
import info.laht.threekt.geometries.WireframeGeometry
|
import info.laht.threekt.geometries.WireframeGeometry
|
||||||
|
import info.laht.threekt.materials.MeshBasicMaterial
|
||||||
import info.laht.threekt.objects.LineSegments
|
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
|
||||||
@ -19,56 +21,33 @@ import kotlin.reflect.KClass
|
|||||||
/**
|
/**
|
||||||
* Basic geometry-based factory
|
* Basic geometry-based factory
|
||||||
*/
|
*/
|
||||||
abstract class MeshThreeFactory<T : VisualObject3D>(
|
abstract class MeshThreeFactory<in T : VisualObject3D>(
|
||||||
override val type: KClass<out T>
|
override val type: KClass<in T>
|
||||||
) : ThreeFactory<T> {
|
) : ThreeFactory<T> {
|
||||||
/**
|
/**
|
||||||
* Build a geometry for an object
|
* Build a geometry for an object
|
||||||
*/
|
*/
|
||||||
abstract fun buildGeometry(obj: T): BufferGeometry
|
abstract fun buildGeometry(obj: T): BufferGeometry
|
||||||
|
|
||||||
private fun Mesh.applyEdges(obj: T) {
|
|
||||||
children.find { it.name == "edges" }?.let { remove(it) }
|
|
||||||
//inherited edges definition, enabled by default
|
|
||||||
if (obj.getProperty(EDGES_ENABLED_KEY).boolean != false) {
|
|
||||||
val material = obj.getProperty(EDGES_MATERIAL_KEY).node.jsLineMaterial()
|
|
||||||
add(
|
|
||||||
LineSegments(
|
|
||||||
EdgesGeometry(geometry as BufferGeometry),
|
|
||||||
material
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Mesh.applyWireFrame(obj: T) {
|
|
||||||
children.find { it.name == "wireframe" }?.let { remove(it) }
|
|
||||||
//inherited wireframe definition, disabled by default
|
|
||||||
if (obj.getProperty(WIREFRAME_ENABLED_KEY).boolean == true) {
|
|
||||||
val material = obj.getProperty(WIREFRAME_MATERIAL_KEY).node.jsLineMaterial()
|
|
||||||
add(
|
|
||||||
LineSegments(
|
|
||||||
WireframeGeometry(geometry as BufferGeometry),
|
|
||||||
material
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun invoke(obj: T): Mesh {
|
override fun invoke(obj: T): Mesh {
|
||||||
//TODO add caching for geometries using templates
|
|
||||||
val geometry = buildGeometry(obj)
|
val geometry = buildGeometry(obj)
|
||||||
|
|
||||||
//JS sometimes tries to pass Geometry as BufferGeometry
|
//JS sometimes tries to pass Geometry as BufferGeometry
|
||||||
@Suppress("USELESS_IS_CHECK") if (geometry !is BufferGeometry) error("BufferGeometry expected")
|
@Suppress("USELESS_IS_CHECK") if (geometry !is BufferGeometry) error("BufferGeometry expected")
|
||||||
|
|
||||||
val mesh = Mesh(geometry, obj.material.jsMaterial()).apply {
|
//val meshMeta: Meta = obj.properties[Material3D.MATERIAL_KEY]?.node ?: Meta.empty
|
||||||
|
|
||||||
|
val mesh = Mesh(geometry, MeshBasicMaterial()).apply {
|
||||||
matrixAutoUpdate = false
|
matrixAutoUpdate = false
|
||||||
applyEdges(obj)
|
applyEdges(obj)
|
||||||
applyWireFrame(obj)
|
applyWireFrame(obj)
|
||||||
|
|
||||||
//set position for mesh
|
//set position for mesh
|
||||||
updatePosition(obj)
|
updatePosition(obj)
|
||||||
|
|
||||||
|
//set color for mesh
|
||||||
|
updateMaterial(obj)
|
||||||
|
|
||||||
layers.enable(obj.layer)
|
layers.enable(obj.layer)
|
||||||
children.forEach {
|
children.forEach {
|
||||||
it.layers.enable(obj.layer)
|
it.layers.enable(obj.layer)
|
||||||
@ -78,7 +57,14 @@ abstract class MeshThreeFactory<T : VisualObject3D>(
|
|||||||
//add listener to object properties
|
//add listener to object properties
|
||||||
obj.onPropertyChange(this) { name, _, _ ->
|
obj.onPropertyChange(this) { name, _, _ ->
|
||||||
when {
|
when {
|
||||||
name.startsWith(VisualObject3D.GEOMETRY_KEY) -> mesh.geometry = buildGeometry(obj)
|
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(WIREFRAME_KEY) -> mesh.applyWireFrame(obj)
|
||||||
name.startsWith(EDGES_KEY) -> mesh.applyEdges(obj)
|
name.startsWith(EDGES_KEY) -> mesh.applyEdges(obj)
|
||||||
else -> mesh.updateProperty(obj, name)
|
else -> mesh.updateProperty(obj, name)
|
||||||
@ -97,3 +83,42 @@ abstract class MeshThreeFactory<T : VisualObject3D>(
|
|||||||
val WIREFRAME_MATERIAL_KEY = WIREFRAME_KEY + Material3D.MATERIAL_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,105 @@
|
|||||||
|
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.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 info.laht.threekt.WebGLRenderer
|
||||||
|
import info.laht.threekt.cameras.PerspectiveCamera
|
||||||
|
import info.laht.threekt.external.controls.OrbitControls
|
||||||
|
import info.laht.threekt.external.controls.TrackballControls
|
||||||
|
import info.laht.threekt.helpers.AxesHelper
|
||||||
|
import info.laht.threekt.scenes.Scene
|
||||||
|
import org.w3c.dom.HTMLElement
|
||||||
|
import org.w3c.dom.Node
|
||||||
|
import kotlin.browser.window
|
||||||
|
import kotlin.dom.clear
|
||||||
|
import kotlin.math.cos
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.math.sin
|
||||||
|
|
||||||
|
class ThreeCanvas(val three: ThreePlugin, val spec: CanvasSpec) : Renderer<VisualObject3D> {
|
||||||
|
|
||||||
|
override val context: Context get() = three.context
|
||||||
|
|
||||||
|
var content: VisualObject3D? = null
|
||||||
|
private set
|
||||||
|
|
||||||
|
val axes = AxesHelper(spec.axes.size.toInt()).apply {
|
||||||
|
visible = spec.axes.visible
|
||||||
|
}
|
||||||
|
|
||||||
|
val scene: Scene = Scene().apply {
|
||||||
|
add(axes)
|
||||||
|
}
|
||||||
|
|
||||||
|
val camera = buildCamera(spec.camera)
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun attach(element: HTMLElement) {
|
||||||
|
element.clear()
|
||||||
|
|
||||||
|
camera.aspect = 1.0
|
||||||
|
|
||||||
|
val renderer = WebGLRenderer { antialias = true }.apply {
|
||||||
|
setClearColor(Colors.skyblue, 1)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
addControls(renderer.domElement, spec.controls)
|
||||||
|
|
||||||
|
fun animate() {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun render(obj: VisualObject3D, meta: Meta) {
|
||||||
|
content = obj
|
||||||
|
val object3D = three.buildObject3D(obj)
|
||||||
|
scene.add(object3D)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ThreePlugin.output(element: HTMLElement? = null, spec: CanvasSpec = CanvasSpec.empty()): ThreeCanvas =
|
||||||
|
ThreeCanvas(this, spec).apply {
|
||||||
|
if (element != null) {
|
||||||
|
attach(element)
|
||||||
|
}
|
||||||
|
}
|
@ -16,9 +16,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
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ 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)
|
||||||
setRotationFromEuler(obj.euler)
|
setRotationFromEuler(obj.euler)
|
||||||
@ -38,22 +38,24 @@ internal fun Object3D.updatePosition(obj: VisualObject3D) {
|
|||||||
updateMatrix()
|
updateMatrix()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
///**
|
||||||
* Unsafe invocation of a factory
|
// * Unsafe invocation of a factory
|
||||||
*/
|
// */
|
||||||
operator fun <T : VisualObject3D> ThreeFactory<T>.invoke(obj: Any): Object3D {
|
//operator fun <T : VisualObject3D> ThreeFactory<T>.invoke(obj: Any): Object3D {
|
||||||
if (type.isInstance(obj)) {
|
// if (type.isInstance(obj)) {
|
||||||
@Suppress("UNCHECKED_CAST")
|
// @Suppress("UNCHECKED_CAST")
|
||||||
return invoke(obj as T)
|
// return invoke(obj as T)
|
||||||
} else {
|
// } else {
|
||||||
error("The object of type ${obj::class} could not be rendered by this factory")
|
// error("The object of type ${obj::class} could not be rendered by this factory")
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update non-position non-geometry property
|
||||||
|
*/
|
||||||
fun Object3D.updateProperty(source: VisualObject, propertyName: Name) {
|
fun Object3D.updateProperty(source: VisualObject, propertyName: Name) {
|
||||||
if (this is Mesh && propertyName.startsWith(MATERIAL_KEY)) {
|
if (this is Mesh && propertyName.startsWith(MATERIAL_KEY)) {
|
||||||
//updated material
|
updateMaterial(source)
|
||||||
material = source.material.jsMaterial()
|
|
||||||
} else if (
|
} else if (
|
||||||
source is VisualObject3D &&
|
source is VisualObject3D &&
|
||||||
(propertyName.startsWith(VisualObject3D.position)
|
(propertyName.startsWith(VisualObject3D.position)
|
||||||
|
@ -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>()
|
||||||
|
@ -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 ThreeLabelFactory : 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
|
||||||
|
}
|
||||||
|
}
|
@ -1,27 +1,32 @@
|
|||||||
package hep.dataforge.vis.spatial.three
|
package hep.dataforge.vis.spatial.three
|
||||||
|
|
||||||
|
import hep.dataforge.meta.node
|
||||||
import hep.dataforge.vis.spatial.PolyLine
|
import hep.dataforge.vis.spatial.PolyLine
|
||||||
import hep.dataforge.vis.spatial.layer
|
import hep.dataforge.vis.spatial.color
|
||||||
import hep.dataforge.vis.spatial.material
|
import hep.dataforge.vis.spatial.three.ThreeMaterials.DEFAULT_LINE_COLOR
|
||||||
import info.laht.threekt.core.Geometry
|
import info.laht.threekt.core.Geometry
|
||||||
import info.laht.threekt.core.Object3D
|
import info.laht.threekt.core.Object3D
|
||||||
|
import info.laht.threekt.math.Color
|
||||||
import info.laht.threekt.objects.LineSegments
|
import info.laht.threekt.objects.LineSegments
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
object ThreeLineFactory : ThreeFactory<PolyLine> {
|
object ThreeLineFactory : ThreeFactory<PolyLine> {
|
||||||
override val type: KClass<out PolyLine> get() = PolyLine::class
|
override val type: KClass<PolyLine> get() = PolyLine::class
|
||||||
|
|
||||||
override fun invoke(obj: PolyLine): Object3D {
|
override fun invoke(obj: PolyLine): Object3D {
|
||||||
val geometry = Geometry().apply {
|
val geometry = Geometry().apply {
|
||||||
vertices = obj.points.toTypedArray()
|
vertices = obj.points.toTypedArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
val material = obj.material.jsLineMaterial()
|
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 {
|
return LineSegments(geometry, material).apply {
|
||||||
|
|
||||||
updatePosition(obj)
|
updatePosition(obj)
|
||||||
layers.enable(obj.layer)
|
//layers.enable(obj.layer)
|
||||||
|
|
||||||
//add listener to object properties
|
//add listener to object properties
|
||||||
obj.onPropertyChange(this) { propertyName, _, _ ->
|
obj.onPropertyChange(this) { propertyName, _, _ ->
|
||||||
updateProperty(obj, propertyName)
|
updateProperty(obj, propertyName)
|
||||||
|
@ -0,0 +1,113 @@
|
|||||||
|
package hep.dataforge.vis.spatial.three
|
||||||
|
|
||||||
|
import hep.dataforge.meta.*
|
||||||
|
import hep.dataforge.values.ValueType
|
||||||
|
import hep.dataforge.vis.common.Colors
|
||||||
|
import hep.dataforge.vis.common.VisualObject
|
||||||
|
import hep.dataforge.vis.spatial.Material3D
|
||||||
|
import info.laht.threekt.materials.LineBasicMaterial
|
||||||
|
import info.laht.threekt.materials.MeshBasicMaterial
|
||||||
|
import info.laht.threekt.materials.MeshPhongMaterial
|
||||||
|
import info.laht.threekt.math.Color
|
||||||
|
import info.laht.threekt.objects.Mesh
|
||||||
|
|
||||||
|
|
||||||
|
object ThreeMaterials {
|
||||||
|
val DEFAULT_COLOR = Color(Colors.darkgreen)
|
||||||
|
val DEFAULT = MeshPhongMaterial().apply {
|
||||||
|
color.set(DEFAULT_COLOR)
|
||||||
|
}
|
||||||
|
val DEFAULT_LINE_COLOR = Color(Colors.black)
|
||||||
|
val DEFAULT_LINE = LineBasicMaterial().apply {
|
||||||
|
color.set(DEFAULT_LINE_COLOR)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// private val materialCache = HashMap<Meta, Material>()
|
||||||
|
private val lineMaterialCache = HashMap<Meta?, LineBasicMaterial>()
|
||||||
|
|
||||||
|
|
||||||
|
// fun buildMaterial(meta: Meta): Material =
|
||||||
|
// MeshBasicMaterial().apply {
|
||||||
|
// color = meta["color"]?.color() ?: DEFAULT_COLOR
|
||||||
|
// opacity = meta["opacity"]?.double ?: 1.0
|
||||||
|
// transparent = meta["transparent"].boolean ?: (opacity < 1.0)
|
||||||
|
// //node["specularColor"]?.let { specular = it.color() }
|
||||||
|
// //side = 2
|
||||||
|
// }
|
||||||
|
|
||||||
|
fun getLineMaterial(meta: Meta?): LineBasicMaterial = lineMaterialCache.getOrPut(meta) {
|
||||||
|
LineBasicMaterial().apply {
|
||||||
|
color = meta[Material3D.COLOR_KEY]?.color() ?: DEFAULT_LINE_COLOR
|
||||||
|
opacity = meta[Material3D.OPACITY_KEY].double ?: 1.0
|
||||||
|
transparent = opacity < 1.0
|
||||||
|
linewidth = meta["thickness"].double ?: 1.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Infer color based on meta item
|
||||||
|
*/
|
||||||
|
fun MetaItem<*>.color(): 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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///**
|
||||||
|
// * Infer Three material based on meta item
|
||||||
|
// */
|
||||||
|
//fun Meta?.jsMaterial(): Material {
|
||||||
|
// return if (this == null) {
|
||||||
|
// ThreeMaterials.DEFAULT
|
||||||
|
// } else {
|
||||||
|
// ThreeMaterials.buildMaterial(this)
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//fun Meta?.jsLineMaterial(): Material {
|
||||||
|
// return if (this == null) {
|
||||||
|
// ThreeMaterials.DEFAULT_LINE
|
||||||
|
// } else {
|
||||||
|
// ThreeMaterials.buildLineMaterial(this)
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
//fun Material3D?.jsMaterial(): Material = this?.config.jsMaterial()
|
||||||
|
//fun Material3D?.jsLineMaterial(): Material = this?.config.jsLineMaterial()
|
||||||
|
|
||||||
|
fun Mesh.updateMaterial(obj: VisualObject) {
|
||||||
|
val meta = obj.getProperty(Material3D.MATERIAL_KEY).node ?: EmptyMeta
|
||||||
|
material = if(meta[Material3D.SPECULAR_COLOR]!= null){
|
||||||
|
MeshPhongMaterial().apply {
|
||||||
|
color = meta[Material3D.COLOR_KEY]?.color() ?: ThreeMaterials.DEFAULT_COLOR
|
||||||
|
specular = meta[Material3D.SPECULAR_COLOR]!!.color()
|
||||||
|
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]?.color() ?: ThreeMaterials.DEFAULT_COLOR
|
||||||
|
opacity = meta[Material3D.OPACITY_KEY]?.double ?: 1.0
|
||||||
|
transparent = opacity < 1.0
|
||||||
|
wireframe = meta[Material3D.WIREFRAME_KEY].boolean ?: false
|
||||||
|
needsUpdate = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,76 +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.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.HTMLElement
|
|
||||||
import kotlin.browser.window
|
|
||||||
import kotlin.dom.clear
|
|
||||||
import kotlin.math.max
|
|
||||||
|
|
||||||
class ThreeOutput(val three: ThreePlugin, val meta: Meta = EmptyMeta) : Output<VisualObject3D> {
|
|
||||||
|
|
||||||
override val context: Context get() = three.context
|
|
||||||
|
|
||||||
val axes = AxesHelper(meta["axes.size"].int ?: 50).apply { visible = false }
|
|
||||||
|
|
||||||
val scene: Scene = Scene().apply {
|
|
||||||
add(AmbientLight())
|
|
||||||
if (meta["axes.visible"].boolean == true) {
|
|
||||||
axes.visible = true
|
|
||||||
}
|
|
||||||
add(axes)
|
|
||||||
}
|
|
||||||
|
|
||||||
val camera = three.buildCamera(meta["camera"].node ?: EmptyMeta)
|
|
||||||
|
|
||||||
fun attach(element: HTMLElement) {
|
|
||||||
element.clear()
|
|
||||||
|
|
||||||
camera.aspect = 1.0
|
|
||||||
|
|
||||||
val renderer = WebGLRenderer { antialias = true }.apply {
|
|
||||||
setClearColor(Colors.skyblue, 1)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
three.addControls(camera, renderer.domElement, meta["controls"].node ?: EmptyMeta)
|
|
||||||
|
|
||||||
fun animate() {
|
|
||||||
window.requestAnimationFrame {
|
|
||||||
animate()
|
|
||||||
}
|
|
||||||
renderer.render(scene, camera)
|
|
||||||
}
|
|
||||||
|
|
||||||
element.appendChild(renderer.domElement)
|
|
||||||
|
|
||||||
val minSize by meta.number(0).int
|
|
||||||
|
|
||||||
renderer.setSize(max(minSize, element.offsetWidth), max(minSize, element.offsetWidth))
|
|
||||||
|
|
||||||
element.onresize = {
|
|
||||||
renderer.setSize(element.offsetWidth, element.offsetWidth)
|
|
||||||
camera.updateProjectionMatrix()
|
|
||||||
}
|
|
||||||
|
|
||||||
animate()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun render(obj: VisualObject3D, meta: Meta) {
|
|
||||||
scene.add(three.buildObject3D(obj))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun ThreePlugin.output(element: HTMLElement? = null, meta: Meta = EmptyMeta, override: MetaBuilder.() -> Unit = {}) =
|
|
||||||
ThreeOutput(this, buildMeta(meta, override)).apply {
|
|
||||||
if (element != null) {
|
|
||||||
attach(element)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +1,11 @@
|
|||||||
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.names.Name
|
|
||||||
import hep.dataforge.names.asName
|
|
||||||
import hep.dataforge.names.isEmpty
|
|
||||||
import hep.dataforge.names.startsWith
|
|
||||||
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
|
import info.laht.threekt.objects.Group as ThreeGroup
|
||||||
@ -34,26 +24,29 @@ class ThreePlugin : AbstractPlugin() {
|
|||||||
objectFactories[Sphere::class] = ThreeSphereFactory
|
objectFactories[Sphere::class] = ThreeSphereFactory
|
||||||
objectFactories[ConeSegment::class] = ThreeCylinderFactory
|
objectFactories[ConeSegment::class] = ThreeCylinderFactory
|
||||||
objectFactories[PolyLine::class] = ThreeLineFactory
|
objectFactories[PolyLine::class] = ThreeLineFactory
|
||||||
|
objectFactories[Label3D::class] = ThreeLabelFactory
|
||||||
}
|
}
|
||||||
|
|
||||||
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 ThreeVisualObject -> obj.toObject3D()
|
||||||
is Proxy -> proxyFactory(obj)
|
is Proxy -> proxyFactory(obj)
|
||||||
is VisualGroup3D -> {
|
is VisualGroup3D -> {
|
||||||
val group = ThreeGroup()
|
val group = ThreeGroup()
|
||||||
obj.children.forEach { (name, child) ->
|
obj.children.forEach { (token, child) ->
|
||||||
if (child is VisualObject3D && child.ignore != true) {
|
if (child is VisualObject3D && child.ignore != true) {
|
||||||
try {
|
try {
|
||||||
val object3D = buildObject3D(child)
|
val object3D = buildObject3D(child)
|
||||||
object3D.name = name.toString()
|
group[token] = object3D
|
||||||
group.add(object3D)
|
|
||||||
} catch (ex: Throwable) {
|
} catch (ex: Throwable) {
|
||||||
logger.error(ex) { "Failed to render $name" }
|
logger.error(ex) { "Failed to render $child" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -74,12 +67,37 @@ class ThreePlugin : AbstractPlugin() {
|
|||||||
visible = obj.visible ?: true
|
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)
|
||||||
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)
|
||||||
@ -89,35 +107,41 @@ 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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Object3D.findChild(name: Name): Object3D? {
|
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 {
|
return when {
|
||||||
name.isEmpty() -> this
|
name.isEmpty() -> this
|
||||||
name.length == 1 -> this.children.find { it.name == name.first()!!.toString() }
|
name.length == 1 -> this.children.find { it.name == name.first()!!.toString() }
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
}
|
@ -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.threeSettings(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()
|
||||||
|
}
|
||||||
|
}
|
@ -1,166 +0,0 @@
|
|||||||
package hep.dataforge.vis.spatial.fx
|
|
||||||
|
|
||||||
import hep.dataforge.vis.spatial.World.CAMERA_FAR_CLIP
|
|
||||||
import hep.dataforge.vis.spatial.World.CAMERA_INITIAL_DISTANCE
|
|
||||||
import hep.dataforge.vis.spatial.World.CAMERA_INITIAL_X_ANGLE
|
|
||||||
import hep.dataforge.vis.spatial.World.CAMERA_INITIAL_Y_ANGLE
|
|
||||||
import hep.dataforge.vis.spatial.World.CAMERA_INITIAL_Z_ANGLE
|
|
||||||
import hep.dataforge.vis.spatial.World.CAMERA_NEAR_CLIP
|
|
||||||
import javafx.event.EventHandler
|
|
||||||
import javafx.scene.*
|
|
||||||
import javafx.scene.input.KeyCode
|
|
||||||
import javafx.scene.input.KeyEvent
|
|
||||||
import javafx.scene.input.MouseEvent
|
|
||||||
import javafx.scene.input.ScrollEvent
|
|
||||||
import javafx.scene.paint.Color
|
|
||||||
import org.fxyz3d.utils.CameraTransformer
|
|
||||||
import tornadofx.*
|
|
||||||
|
|
||||||
class Canvas3D : Fragment() {
|
|
||||||
val world: Group = Group()
|
|
||||||
|
|
||||||
private val camera = PerspectiveCamera().apply {
|
|
||||||
nearClip = CAMERA_NEAR_CLIP
|
|
||||||
farClip = CAMERA_FAR_CLIP
|
|
||||||
translateZ = CAMERA_INITIAL_DISTANCE
|
|
||||||
}
|
|
||||||
|
|
||||||
private val cameraShift = CameraTransformer().apply {
|
|
||||||
val cameraFlip = CameraTransformer()
|
|
||||||
cameraFlip.children.add(camera)
|
|
||||||
cameraFlip.setRotateZ(180.0)
|
|
||||||
children.add(cameraFlip)
|
|
||||||
}
|
|
||||||
|
|
||||||
val translationXProperty get() = cameraShift.t.xProperty()
|
|
||||||
var translateX by translationXProperty
|
|
||||||
val translationYProperty get() = cameraShift.t.yProperty()
|
|
||||||
var translateY by translationYProperty
|
|
||||||
val translationZProperty get() = cameraShift.t.zProperty()
|
|
||||||
var translateZ by translationZProperty
|
|
||||||
|
|
||||||
private val cameraRotation = CameraTransformer().apply {
|
|
||||||
children.add(cameraShift)
|
|
||||||
ry.angle = CAMERA_INITIAL_Y_ANGLE
|
|
||||||
rx.angle = CAMERA_INITIAL_X_ANGLE
|
|
||||||
rz.angle = CAMERA_INITIAL_Z_ANGLE
|
|
||||||
}
|
|
||||||
|
|
||||||
val rotationXProperty get() = cameraRotation.rx.angleProperty()
|
|
||||||
var angleX by rotationXProperty
|
|
||||||
val rotationYProperty get() = cameraRotation.ry.angleProperty()
|
|
||||||
var angleY by rotationYProperty
|
|
||||||
val rotationZProperty get() = cameraRotation.rz.angleProperty()
|
|
||||||
var angleZ by rotationZProperty
|
|
||||||
|
|
||||||
|
|
||||||
override val root = borderpane {
|
|
||||||
center = SubScene(
|
|
||||||
Group(world, cameraRotation).apply { DepthTest.ENABLE },
|
|
||||||
1024.0,
|
|
||||||
768.0,
|
|
||||||
true,
|
|
||||||
SceneAntialiasing.BALANCED
|
|
||||||
).apply {
|
|
||||||
fill = Color.GREY
|
|
||||||
this.camera = this@Canvas3D.camera
|
|
||||||
id = "canvas"
|
|
||||||
handleKeyboard(this)
|
|
||||||
handleMouse(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private fun handleKeyboard(scene: SubScene) {
|
|
||||||
scene.onKeyPressed = EventHandler<KeyEvent> { event ->
|
|
||||||
if (event.isControlDown) {
|
|
||||||
when (event.code) {
|
|
||||||
KeyCode.Z -> {
|
|
||||||
cameraShift.t.x = 0.0
|
|
||||||
cameraShift.t.y = 0.0
|
|
||||||
camera.translateZ = CAMERA_INITIAL_DISTANCE
|
|
||||||
cameraRotation.ry.angle = CAMERA_INITIAL_Y_ANGLE
|
|
||||||
cameraRotation.rx.angle = CAMERA_INITIAL_X_ANGLE
|
|
||||||
}
|
|
||||||
// KeyCode.X -> axisGroup.isVisible = !axisGroup.isVisible
|
|
||||||
// KeyCode.S -> snapshot()
|
|
||||||
// KeyCode.DIGIT1 -> pixelMap.filterKeys { it.getLayerNumber() == 1 }.values.forEach {
|
|
||||||
// toggleTransparency(
|
|
||||||
// it
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// KeyCode.DIGIT2 -> pixelMap.filterKeys { it.getLayerNumber() == 2 }.values.forEach {
|
|
||||||
// toggleTransparency(
|
|
||||||
// it
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// KeyCode.DIGIT3 -> pixelMap.filterKeys { it.getLayerNumber() == 3 }.values.forEach {
|
|
||||||
// toggleTransparency(
|
|
||||||
// it
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
else -> {
|
|
||||||
}//do nothing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleMouse(scene: SubScene) {
|
|
||||||
|
|
||||||
var mousePosX: Double = 0.0
|
|
||||||
var mousePosY: Double = 0.0
|
|
||||||
var mouseOldX: Double = 0.0
|
|
||||||
var mouseOldY: Double = 0.0
|
|
||||||
var mouseDeltaX: Double = 0.0
|
|
||||||
var mouseDeltaY: Double = 0.0
|
|
||||||
|
|
||||||
scene.onMousePressed = EventHandler<MouseEvent> { me ->
|
|
||||||
mousePosX = me.sceneX
|
|
||||||
mousePosY = me.sceneY
|
|
||||||
mouseOldX = me.sceneX
|
|
||||||
mouseOldY = me.sceneY
|
|
||||||
}
|
|
||||||
|
|
||||||
scene.onMouseDragged = EventHandler<MouseEvent> { me ->
|
|
||||||
mouseOldX = mousePosX
|
|
||||||
mouseOldY = mousePosY
|
|
||||||
mousePosX = me.sceneX
|
|
||||||
mousePosY = me.sceneY
|
|
||||||
mouseDeltaX = mousePosX - mouseOldX
|
|
||||||
mouseDeltaY = mousePosY - mouseOldY
|
|
||||||
|
|
||||||
val modifier = when {
|
|
||||||
me.isControlDown -> CONTROL_MULTIPLIER
|
|
||||||
me.isShiftDown -> SHIFT_MULTIPLIER
|
|
||||||
else -> 1.0
|
|
||||||
}
|
|
||||||
|
|
||||||
if (me.isPrimaryButtonDown) {
|
|
||||||
cameraRotation.rz.angle =
|
|
||||||
cameraRotation.rz.angle + mouseDeltaX * MOUSE_SPEED * modifier * ROTATION_SPEED
|
|
||||||
cameraRotation.rx.angle =
|
|
||||||
cameraRotation.rx.angle + mouseDeltaY * MOUSE_SPEED * modifier * ROTATION_SPEED
|
|
||||||
} else if (me.isSecondaryButtonDown) {
|
|
||||||
cameraShift.t.x = cameraShift.t.x + mouseDeltaX * MOUSE_SPEED * modifier * TRACK_SPEED
|
|
||||||
cameraShift.t.y = cameraShift.t.y + mouseDeltaY * MOUSE_SPEED * modifier * TRACK_SPEED
|
|
||||||
}
|
|
||||||
}
|
|
||||||
scene.onScroll = EventHandler<ScrollEvent> { event ->
|
|
||||||
val z = camera.translateZ
|
|
||||||
val newZ = z + MOUSE_SPEED * event.deltaY * RESIZE_SPEED
|
|
||||||
camera.translateZ = newZ
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val AXIS_LENGTH = 2000.0
|
|
||||||
private const val CONTROL_MULTIPLIER = 0.1
|
|
||||||
private const val SHIFT_MULTIPLIER = 10.0
|
|
||||||
private const val MOUSE_SPEED = 0.1
|
|
||||||
private const val ROTATION_SPEED = 2.0
|
|
||||||
private const val TRACK_SPEED = 6.0
|
|
||||||
private const val RESIZE_SPEED = 50.0
|
|
||||||
private const val LINE_WIDTH = 3.0
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
package hep.dataforge.vis.spatial.fx
|
|
||||||
|
|
||||||
import hep.dataforge.meta.*
|
|
||||||
import hep.dataforge.names.Name
|
|
||||||
import hep.dataforge.names.toName
|
|
||||||
import hep.dataforge.vis.common.VisualObject
|
|
||||||
import javafx.beans.binding.ObjectBinding
|
|
||||||
import tornadofx.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A caching binding collection for [VisualObject] properties
|
|
||||||
*/
|
|
||||||
class DisplayObjectFXListener(val obj: VisualObject) {
|
|
||||||
private val binndings = HashMap<Name, ObjectBinding<MetaItem<*>?>>()
|
|
||||||
|
|
||||||
init {
|
|
||||||
obj.onPropertyChange(this) { name, _, _ ->
|
|
||||||
binndings[name]?.invalidate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
operator fun get(key: Name): ObjectBinding<MetaItem<*>?> {
|
|
||||||
return binndings.getOrPut(key) {
|
|
||||||
object : ObjectBinding<MetaItem<*>?>() {
|
|
||||||
override fun computeValue(): MetaItem<*>? = obj.getProperty(key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
operator fun get(key: String) = get(key.toName())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun ObjectBinding<MetaItem<*>?>.value() = this.objectBinding { it.value }
|
|
||||||
fun ObjectBinding<MetaItem<*>?>.string() = this.stringBinding { it.string }
|
|
||||||
fun ObjectBinding<MetaItem<*>?>.number() = this.objectBinding { it.number }
|
|
||||||
fun ObjectBinding<MetaItem<*>?>.double() = this.objectBinding { it.double }
|
|
||||||
fun ObjectBinding<MetaItem<*>?>.float() = this.objectBinding { it.number?.toFloat() }
|
|
||||||
fun ObjectBinding<MetaItem<*>?>.int() = this.objectBinding { it.int }
|
|
||||||
fun ObjectBinding<MetaItem<*>?>.long() = this.objectBinding { it.long }
|
|
||||||
fun ObjectBinding<MetaItem<*>?>.node() = this.objectBinding { it.node }
|
|
||||||
|
|
||||||
fun <T> ObjectBinding<MetaItem<*>?>.transform(transform: (MetaItem<*>) -> T) = this.objectBinding { it?.let(transform) }
|
|
@ -1,50 +0,0 @@
|
|||||||
package hep.dataforge.vis.spatial.fx
|
|
||||||
|
|
||||||
import hep.dataforge.context.Context
|
|
||||||
import hep.dataforge.meta.Meta
|
|
||||||
import hep.dataforge.output.Output
|
|
||||||
import hep.dataforge.vis.common.VisualObject
|
|
||||||
import hep.dataforge.vis.spatial.Box
|
|
||||||
import hep.dataforge.vis.spatial.VisualGroup3D
|
|
||||||
import javafx.scene.Group
|
|
||||||
import javafx.scene.Node
|
|
||||||
import org.fxyz3d.shapes.primitives.CuboidMesh
|
|
||||||
import tornadofx.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://github.com/miho/JCSG for operations
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
class FX3DOutput(override val context: Context) : Output<VisualObject> {
|
|
||||||
val canvas by lazy { Canvas3D() }
|
|
||||||
|
|
||||||
|
|
||||||
private fun buildNode(obj: VisualObject): Node? {
|
|
||||||
val listener = DisplayObjectFXListener(obj)
|
|
||||||
val x = listener["pos.x"].float()
|
|
||||||
val y = listener["pos.y"].float()
|
|
||||||
val z = listener["pos.z"].float()
|
|
||||||
val center = objectBinding(x, y, z) {
|
|
||||||
org.fxyz3d.geometry.Point3D(x.value ?: 0f, y.value ?: 0f, z.value ?: 0f)
|
|
||||||
}
|
|
||||||
return when (obj) {
|
|
||||||
is VisualGroup3D -> Group(obj.map { buildNode(it) }).apply {
|
|
||||||
this.translateXProperty().bind(x)
|
|
||||||
this.translateYProperty().bind(y)
|
|
||||||
this.translateZProperty().bind(z)
|
|
||||||
}
|
|
||||||
is Box -> CuboidMesh(obj.xSize.toDouble(), obj.ySize.toDouble(), obj.zSize.toDouble()).apply {
|
|
||||||
this.centerProperty().bind(center)
|
|
||||||
this.materialProperty().bind(listener["color"].transform { it.material() })
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
logger.error { "No renderer defined for ${obj::class}" }
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun render(obj: VisualObject, meta: Meta) {
|
|
||||||
buildNode(obj)?.let { canvas.world.children.add(it) }
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,159 @@
|
|||||||
|
package hep.dataforge.vis.spatial.fx
|
||||||
|
|
||||||
|
import hep.dataforge.context.*
|
||||||
|
import hep.dataforge.meta.Meta
|
||||||
|
import hep.dataforge.meta.boolean
|
||||||
|
import hep.dataforge.provider.Type
|
||||||
|
import hep.dataforge.vis.spatial.*
|
||||||
|
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_KEY
|
||||||
|
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_WIREFRAME_KEY
|
||||||
|
import hep.dataforge.vis.spatial.fx.FX3DFactory.Companion.TYPE
|
||||||
|
import javafx.scene.Group
|
||||||
|
import javafx.scene.Node
|
||||||
|
import javafx.scene.shape.CullFace
|
||||||
|
import javafx.scene.shape.DrawMode
|
||||||
|
import javafx.scene.shape.Shape3D
|
||||||
|
import javafx.scene.text.Font
|
||||||
|
import javafx.scene.text.Text
|
||||||
|
import javafx.scene.transform.Rotate
|
||||||
|
import org.fxyz3d.shapes.composites.PolyLine3D
|
||||||
|
import org.fxyz3d.shapes.primitives.CuboidMesh
|
||||||
|
import org.fxyz3d.shapes.primitives.SpheroidMesh
|
||||||
|
import kotlin.collections.HashMap
|
||||||
|
import kotlin.collections.component1
|
||||||
|
import kotlin.collections.component2
|
||||||
|
import kotlin.collections.find
|
||||||
|
import kotlin.collections.map
|
||||||
|
import kotlin.collections.mapNotNull
|
||||||
|
import kotlin.collections.set
|
||||||
|
import kotlin.math.PI
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
class FX3DPlugin : AbstractPlugin() {
|
||||||
|
override val tag: PluginTag get() = Companion.tag
|
||||||
|
|
||||||
|
private val objectFactories = HashMap<KClass<out VisualObject3D>, FX3DFactory<*>>()
|
||||||
|
private val compositeFactory = FXCompositeFactory(this)
|
||||||
|
private val proxyFactory = FXProxyFactory(this)
|
||||||
|
|
||||||
|
init {
|
||||||
|
//Add specialized factories here
|
||||||
|
objectFactories[Convex::class] = FXConvexFactory
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
private fun findObjectFactory(type: KClass<out VisualObject3D>): FX3DFactory<VisualObject3D>? {
|
||||||
|
return (objectFactories[type] ?: context.content<FX3DFactory<*>>(TYPE).values.find { it.type == type })
|
||||||
|
as FX3DFactory<VisualObject3D>?
|
||||||
|
}
|
||||||
|
|
||||||
|
fun buildNode(obj: VisualObject3D): Node {
|
||||||
|
val binding = VisualObjectFXBinding(obj)
|
||||||
|
return when (obj) {
|
||||||
|
is Proxy -> proxyFactory(obj, binding)
|
||||||
|
is VisualGroup3D -> {
|
||||||
|
Group(obj.children.mapNotNull { (token, obj) ->
|
||||||
|
(obj as? VisualObject3D)?.let {
|
||||||
|
buildNode(it).apply {
|
||||||
|
properties["name"] = token.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
is Composite -> compositeFactory(obj, binding)
|
||||||
|
is Box -> CuboidMesh(obj.xSize.toDouble(), obj.ySize.toDouble(), obj.zSize.toDouble())
|
||||||
|
is Sphere -> if (obj.phi == PI2 && obj.theta == PI.toFloat()) {
|
||||||
|
//use sphere for orb
|
||||||
|
SpheroidMesh(obj.detail ?: 16, obj.radius.toDouble(), obj.radius.toDouble())
|
||||||
|
} else {
|
||||||
|
FXShapeFactory(obj, binding)
|
||||||
|
}
|
||||||
|
is Label3D -> Text(obj.text).apply {
|
||||||
|
font = Font.font(obj.fontFamily, obj.fontSize)
|
||||||
|
x = -layoutBounds.width / 2
|
||||||
|
y = layoutBounds.height / 2
|
||||||
|
}
|
||||||
|
is PolyLine -> PolyLine3D(
|
||||||
|
obj.points.map { it.point },
|
||||||
|
obj.thickness.toFloat(),
|
||||||
|
obj.getProperty(Material3D.MATERIAL_COLOR_KEY)?.color()
|
||||||
|
).apply {
|
||||||
|
this.meshView.cullFace = CullFace.FRONT
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
//find specialized factory for this type if it is present
|
||||||
|
val factory: FX3DFactory<VisualObject3D>? = findObjectFactory(obj::class)
|
||||||
|
when {
|
||||||
|
factory != null -> factory(obj, binding)
|
||||||
|
obj is Shape -> FXShapeFactory(obj, binding)
|
||||||
|
else -> error("Renderer for ${obj::class} not found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.apply {
|
||||||
|
translateXProperty().bind(binding[VisualObject3D.xPos].float(obj.x.toFloat()))
|
||||||
|
translateYProperty().bind(binding[VisualObject3D.yPos].float(obj.y.toFloat()))
|
||||||
|
translateZProperty().bind(binding[VisualObject3D.zPos].float(obj.z.toFloat()))
|
||||||
|
scaleXProperty().bind(binding[VisualObject3D.xScale].float(obj.scaleX.toFloat()))
|
||||||
|
scaleYProperty().bind(binding[VisualObject3D.yScale].float(obj.scaleY.toFloat()))
|
||||||
|
scaleZProperty().bind(binding[VisualObject3D.zScale].float(obj.scaleZ.toFloat()))
|
||||||
|
|
||||||
|
val rotateX = Rotate(0.0, Rotate.X_AXIS).apply {
|
||||||
|
angleProperty().bind(binding[VisualObject3D.xRotation].float(obj.rotationX.toFloat()).multiply(180.0 / PI))
|
||||||
|
}
|
||||||
|
|
||||||
|
val rotateY = Rotate(0.0, Rotate.Y_AXIS).apply {
|
||||||
|
angleProperty().bind(binding[VisualObject3D.yRotation].float(obj.rotationY.toFloat()).multiply(180.0 / PI))
|
||||||
|
}
|
||||||
|
|
||||||
|
val rotateZ = Rotate(0.0, Rotate.Z_AXIS).apply {
|
||||||
|
angleProperty().bind(binding[VisualObject3D.zRotation].float(obj.rotationZ.toFloat()).multiply(180.0 / PI))
|
||||||
|
}
|
||||||
|
|
||||||
|
when (obj.rotationOrder) {
|
||||||
|
RotationOrder.ZYX -> transforms.addAll(rotateZ, rotateY, rotateX)
|
||||||
|
RotationOrder.XZY -> transforms.addAll(rotateX, rotateZ, rotateY)
|
||||||
|
RotationOrder.YXZ -> transforms.addAll(rotateY, rotateX, rotateZ)
|
||||||
|
RotationOrder.YZX -> transforms.addAll(rotateY, rotateZ, rotateX)
|
||||||
|
RotationOrder.ZXY -> transforms.addAll(rotateZ, rotateX, rotateY)
|
||||||
|
RotationOrder.XYZ -> transforms.addAll(rotateX, rotateY, rotateZ)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this is Shape3D) {
|
||||||
|
materialProperty().bind(binding[MATERIAL_KEY].transform {
|
||||||
|
it.material()
|
||||||
|
})
|
||||||
|
|
||||||
|
drawModeProperty().bind(binding[MATERIAL_WIREFRAME_KEY].transform {
|
||||||
|
if (it.boolean == true) {
|
||||||
|
DrawMode.LINE
|
||||||
|
} else {
|
||||||
|
DrawMode.FILL
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object : PluginFactory<FX3DPlugin> {
|
||||||
|
override val tag = PluginTag("visual.fx3D", PluginTag.DATAFORGE_GROUP)
|
||||||
|
override val type = FX3DPlugin::class
|
||||||
|
override fun invoke(meta: Meta, context: Context) = FX3DPlugin()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder and updater for three.js object
|
||||||
|
*/
|
||||||
|
@Type(TYPE)
|
||||||
|
interface FX3DFactory<in T : VisualObject3D> {
|
||||||
|
|
||||||
|
val type: KClass<in T>
|
||||||
|
|
||||||
|
operator fun invoke(obj: T, binding: VisualObjectFXBinding): Node
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TYPE = "fx3DFactory"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,86 @@
|
|||||||
|
package hep.dataforge.vis.spatial.fx
|
||||||
|
|
||||||
|
import hep.dataforge.context.Context
|
||||||
|
import hep.dataforge.context.ContextAware
|
||||||
|
import hep.dataforge.meta.Meta
|
||||||
|
import hep.dataforge.output.Renderer
|
||||||
|
import hep.dataforge.vis.spatial.VisualObject3D
|
||||||
|
import hep.dataforge.vis.spatial.specifications.CanvasSpec
|
||||||
|
import javafx.application.Platform
|
||||||
|
import javafx.beans.property.ObjectProperty
|
||||||
|
import javafx.beans.property.SimpleObjectProperty
|
||||||
|
import javafx.scene.*
|
||||||
|
import javafx.scene.paint.Color
|
||||||
|
import org.fxyz3d.scene.Axes
|
||||||
|
import tornadofx.*
|
||||||
|
|
||||||
|
class FXCanvas3D(val plugin: FX3DPlugin, val spec: CanvasSpec = CanvasSpec.empty()) :
|
||||||
|
Fragment(), Renderer<VisualObject3D>, ContextAware {
|
||||||
|
|
||||||
|
override val context: Context get() = plugin.context
|
||||||
|
|
||||||
|
val world = Group().apply {
|
||||||
|
//transforms.add(Rotate(180.0, Rotate.Z_AXIS))
|
||||||
|
}
|
||||||
|
|
||||||
|
val axes = Axes().also {
|
||||||
|
it.setHeight(spec.axes.size)
|
||||||
|
it.setRadius(spec.axes.width)
|
||||||
|
it.isVisible = spec.axes.visible
|
||||||
|
world.add(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
val light = AmbientLight()
|
||||||
|
|
||||||
|
private val camera = PerspectiveCamera().apply {
|
||||||
|
nearClip = spec.camera.nearClip
|
||||||
|
farClip = spec.camera.farClip
|
||||||
|
fieldOfView = spec.camera.fov.toDouble()
|
||||||
|
this.add(light)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val canvas = SubScene(
|
||||||
|
Group(world, camera).apply { DepthTest.ENABLE },
|
||||||
|
400.0,
|
||||||
|
400.0,
|
||||||
|
true,
|
||||||
|
SceneAntialiasing.BALANCED
|
||||||
|
).also { scene ->
|
||||||
|
scene.fill = Color.GREY
|
||||||
|
scene.camera = camera
|
||||||
|
}
|
||||||
|
|
||||||
|
override val root = borderpane {
|
||||||
|
center = canvas
|
||||||
|
}
|
||||||
|
|
||||||
|
val controls = camera.orbitControls(canvas, spec.camera).also {
|
||||||
|
world.add(it.centerMarker)
|
||||||
|
}
|
||||||
|
|
||||||
|
val rootObjectProperty: ObjectProperty<VisualObject3D> = SimpleObjectProperty()
|
||||||
|
var rootObject: VisualObject3D? by rootObjectProperty
|
||||||
|
|
||||||
|
private val rootNodeProperty = rootObjectProperty.objectBinding {
|
||||||
|
it?.let { plugin.buildNode(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
canvas.widthProperty().bind(root.widthProperty())
|
||||||
|
canvas.heightProperty().bind(root.heightProperty())
|
||||||
|
rootNodeProperty.addListener { _, oldValue: Node?, newValue: Node? ->
|
||||||
|
Platform.runLater {
|
||||||
|
if (oldValue != null) {
|
||||||
|
world.children.remove(oldValue)
|
||||||
|
}
|
||||||
|
if (newValue != null) {
|
||||||
|
world.children.add(newValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun render(obj: VisualObject3D, meta: Meta) {
|
||||||
|
rootObject = obj
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
package hep.dataforge.vis.spatial.fx
|
||||||
|
|
||||||
|
import eu.mihosoft.jcsg.CSG
|
||||||
|
import eu.mihosoft.jcsg.Polygon
|
||||||
|
import eu.mihosoft.vvecmath.Vector3d
|
||||||
|
import hep.dataforge.vis.spatial.Composite
|
||||||
|
import hep.dataforge.vis.spatial.CompositeType
|
||||||
|
import javafx.scene.Group
|
||||||
|
import javafx.scene.Node
|
||||||
|
import javafx.scene.shape.MeshView
|
||||||
|
import javafx.scene.shape.TriangleMesh
|
||||||
|
import javafx.scene.shape.VertexFormat
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.collections.HashMap
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
private fun MeshView.toCSG(): CSG {
|
||||||
|
val mesh = this.mesh as TriangleMesh
|
||||||
|
if (mesh.vertexFormat != VertexFormat.POINT_TEXCOORD) error("Not POINT_TEXCOORD")
|
||||||
|
val polygons: MutableList<Polygon> = ArrayList()
|
||||||
|
val faces = mesh.faces
|
||||||
|
val points = mesh.points
|
||||||
|
|
||||||
|
val vectorCache = HashMap<Int, Vector3d>()
|
||||||
|
fun getVector(index: Int) = vectorCache.getOrPut(index) {
|
||||||
|
Vector3d.xyz(
|
||||||
|
points[3 * index].toDouble(),
|
||||||
|
points[3 * index + 1].toDouble(),
|
||||||
|
points[3 * index + 2].toDouble()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i in 0 until faces.size() / 6) {
|
||||||
|
val polygon = Polygon.fromPoints(
|
||||||
|
getVector(faces[6 * i]),
|
||||||
|
getVector(faces[6 * i + 2]),
|
||||||
|
getVector(faces[6 * i + 4])
|
||||||
|
)
|
||||||
|
polygons.add(polygon)
|
||||||
|
}
|
||||||
|
|
||||||
|
return CSG.fromPolygons(polygons)
|
||||||
|
}
|
||||||
|
|
||||||
|
class FXCompositeFactory(val plugin: FX3DPlugin) : FX3DFactory<Composite> {
|
||||||
|
override val type: KClass<in Composite>
|
||||||
|
get() = Composite::class
|
||||||
|
|
||||||
|
override fun invoke(obj: Composite, binding: VisualObjectFXBinding): Node {
|
||||||
|
val first = plugin.buildNode(obj.first) as? MeshView ?: error("Can't build node")
|
||||||
|
val second = plugin.buildNode(obj.second) as? MeshView ?: error("Can't build node")
|
||||||
|
val firstCSG = first.toCSG()
|
||||||
|
val secondCSG = second.toCSG()
|
||||||
|
val resultCSG = when (obj.compositeType) {
|
||||||
|
CompositeType.UNION -> firstCSG.union(secondCSG)
|
||||||
|
CompositeType.INTERSECT -> firstCSG.intersect(secondCSG)
|
||||||
|
CompositeType.SUBTRACT -> firstCSG.difference(secondCSG)
|
||||||
|
}
|
||||||
|
return resultCSG.toNode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun CSG.toNode(): Node {
|
||||||
|
val meshes = toJavaFXMesh().asMeshViews
|
||||||
|
return if (meshes.size == 1) {
|
||||||
|
meshes.first()
|
||||||
|
} else {
|
||||||
|
Group(meshes.map { it })
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package hep.dataforge.vis.spatial.fx
|
||||||
|
|
||||||
|
import eu.mihosoft.jcsg.PropertyStorage
|
||||||
|
import eu.mihosoft.jcsg.ext.quickhull3d.HullUtil
|
||||||
|
import eu.mihosoft.vvecmath.Vector3d
|
||||||
|
import hep.dataforge.vis.spatial.Convex
|
||||||
|
import javafx.scene.Node
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
|
||||||
|
object FXConvexFactory : FX3DFactory<Convex> {
|
||||||
|
override val type: KClass<in Convex> get() = Convex::class
|
||||||
|
|
||||||
|
override fun invoke(obj: Convex, binding: VisualObjectFXBinding): Node {
|
||||||
|
val hull = HullUtil.hull(obj.points.map { Vector3d.xyz(it.x, it.y, it.z) }, PropertyStorage())
|
||||||
|
return hull.toNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -5,14 +5,16 @@ import hep.dataforge.meta.double
|
|||||||
import hep.dataforge.meta.get
|
import hep.dataforge.meta.get
|
||||||
import hep.dataforge.meta.int
|
import hep.dataforge.meta.int
|
||||||
import hep.dataforge.values.ValueType
|
import hep.dataforge.values.ValueType
|
||||||
|
import hep.dataforge.vis.common.Colors
|
||||||
|
import hep.dataforge.vis.spatial.Material3D
|
||||||
import javafx.scene.paint.Color
|
import javafx.scene.paint.Color
|
||||||
import javafx.scene.paint.Material
|
import javafx.scene.paint.Material
|
||||||
import javafx.scene.paint.PhongMaterial
|
import javafx.scene.paint.PhongMaterial
|
||||||
|
|
||||||
object Materials {
|
object FXMaterials {
|
||||||
val RED = PhongMaterial().apply {
|
val RED = PhongMaterial().apply {
|
||||||
diffuseColor = Color.DARKRED
|
diffuseColor = Color.DARKRED
|
||||||
specularColor = Color.RED
|
specularColor = Color.WHITE
|
||||||
}
|
}
|
||||||
|
|
||||||
val WHITE = PhongMaterial().apply {
|
val WHITE = PhongMaterial().apply {
|
||||||
@ -22,7 +24,7 @@ object Materials {
|
|||||||
|
|
||||||
val GREY = PhongMaterial().apply {
|
val GREY = PhongMaterial().apply {
|
||||||
diffuseColor = Color.DARKGREY
|
diffuseColor = Color.DARKGREY
|
||||||
specularColor = Color.GREY
|
specularColor = Color.WHITE
|
||||||
}
|
}
|
||||||
|
|
||||||
val BLUE = PhongMaterial(Color.BLUE)
|
val BLUE = PhongMaterial(Color.BLUE)
|
||||||
@ -30,24 +32,25 @@ object Materials {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Infer color based on meta item
|
* Infer color based on meta item
|
||||||
|
* @param opacity default opacity
|
||||||
*/
|
*/
|
||||||
fun MetaItem<*>.color(): Color {
|
fun MetaItem<*>.color(opacity: Double = 1.0): Color {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
is MetaItem.ValueItem -> if (this.value.type == ValueType.STRING) {
|
is MetaItem.ValueItem -> if (this.value.type == ValueType.NUMBER) {
|
||||||
Color.web(this.value.string)
|
|
||||||
} else {
|
|
||||||
val int = value.number.toInt()
|
val int = value.number.toInt()
|
||||||
val red = int and 0x00ff0000 shr 16
|
val red = int and 0x00ff0000 shr 16
|
||||||
val green = int and 0x0000ff00 shr 8
|
val green = int and 0x0000ff00 shr 8
|
||||||
val blue = int and 0x000000ff
|
val blue = int and 0x000000ff
|
||||||
Color.rgb(red, green, blue)
|
Color.rgb(red, green, blue, opacity)
|
||||||
|
} else {
|
||||||
|
Color.web(this.value.string)
|
||||||
}
|
}
|
||||||
is MetaItem.NodeItem -> {
|
is MetaItem.NodeItem -> {
|
||||||
Color.rgb(
|
Color.rgb(
|
||||||
node["red"]?.int ?: 0,
|
node[Colors.RED_KEY]?.int ?: 0,
|
||||||
node["green"]?.int ?: 0,
|
node[Colors.GREEN_KEY]?.int ?: 0,
|
||||||
node["blue"]?.int ?: 0,
|
node[Colors.BLUE_KEY]?.int ?: 0,
|
||||||
node["opacity"]?.double ?: 1.0
|
node[Material3D.OPACITY_KEY]?.double ?: opacity
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -58,11 +61,12 @@ fun MetaItem<*>.color(): Color {
|
|||||||
*/
|
*/
|
||||||
fun MetaItem<*>?.material(): Material {
|
fun MetaItem<*>?.material(): Material {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
null -> Materials.GREY
|
null -> FXMaterials.GREY
|
||||||
is MetaItem.ValueItem -> PhongMaterial(color())
|
is MetaItem.ValueItem -> PhongMaterial(color())
|
||||||
is MetaItem.NodeItem -> PhongMaterial().apply {
|
is MetaItem.NodeItem -> PhongMaterial().apply {
|
||||||
(node["color"]?: this@material).let { diffuseColor = it.color() }
|
val opacity = node[Material3D.OPACITY_KEY].double ?: 1.0
|
||||||
node["specularColor"]?.let { specularColor = it.color() }
|
diffuseColor = node[Material3D.COLOR_KEY]?.color(opacity) ?: Color.DARKGREY
|
||||||
|
specularColor = node[Material3D.SPECULAR_COLOR]?.color(opacity) ?: Color.WHITE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
package hep.dataforge.vis.spatial.fx
|
||||||
|
|
||||||
|
import hep.dataforge.names.Name
|
||||||
|
import hep.dataforge.names.isEmpty
|
||||||
|
import hep.dataforge.names.toName
|
||||||
|
import hep.dataforge.vis.common.VisualObject
|
||||||
|
import hep.dataforge.vis.spatial.Proxy
|
||||||
|
import javafx.scene.Group
|
||||||
|
import javafx.scene.Node
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
class FXProxyFactory(val plugin: FX3DPlugin) : FX3DFactory<Proxy> {
|
||||||
|
override val type: KClass<in Proxy> get() = Proxy::class
|
||||||
|
|
||||||
|
override fun invoke(obj: Proxy, binding: VisualObjectFXBinding): Node {
|
||||||
|
val template = obj.prototype
|
||||||
|
val node = plugin.buildNode(template)
|
||||||
|
|
||||||
|
obj.onPropertyChange(this) { name, _, _ ->
|
||||||
|
if (name.first()?.body == Proxy.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] ?: error("Proxy child with name '$childName' not found")
|
||||||
|
val child = node.findChild(childName) ?: error("Object child with name '$childName' not found")
|
||||||
|
child.updateProperty(proxyChild, propertyName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Node.findChild(name: Name): Node? {
|
||||||
|
return if (name.isEmpty()) {
|
||||||
|
this
|
||||||
|
} else {
|
||||||
|
(this as? Group)
|
||||||
|
?.children
|
||||||
|
?.find { it.properties["name"] as String == name.first()?.toString() }
|
||||||
|
?.findChild(name.cutFirst())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Node.updateProperty(obj: VisualObject, propertyName: Name) {
|
||||||
|
// if (propertyName.startsWith(Material3D.MATERIAL_KEY)) {
|
||||||
|
// (this as? Shape3D)?.let { it.material = obj.getProperty(Material3D.MATERIAL_KEY).material() }
|
||||||
|
// }
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
package hep.dataforge.vis.spatial.fx
|
||||||
|
|
||||||
|
import hep.dataforge.meta.Meta
|
||||||
|
import hep.dataforge.vis.spatial.GeometryBuilder
|
||||||
|
import hep.dataforge.vis.spatial.Point3D
|
||||||
|
import hep.dataforge.vis.spatial.Shape
|
||||||
|
import javafx.scene.shape.Mesh
|
||||||
|
import javafx.scene.shape.MeshView
|
||||||
|
import javafx.scene.shape.TriangleMesh
|
||||||
|
import javafx.scene.shape.VertexFormat
|
||||||
|
import org.fxyz3d.geometry.Face3
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
object FXShapeFactory : FX3DFactory<Shape> {
|
||||||
|
override val type: KClass<in Shape> get() = Shape::class
|
||||||
|
|
||||||
|
override fun invoke(obj: Shape, binding: VisualObjectFXBinding): MeshView {
|
||||||
|
val mesh = FXGeometryBuilder().apply { obj.toGeometry(this) }.build()
|
||||||
|
return MeshView(mesh)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FXGeometryBuilder : GeometryBuilder<Mesh> {
|
||||||
|
val vertices = ArrayList<Point3D>()
|
||||||
|
val faces = ArrayList<Face3>()
|
||||||
|
private val vertexCache = HashMap<Point3D, Int>()
|
||||||
|
|
||||||
|
private fun append(vertex: Point3D): Int {
|
||||||
|
val index = vertexCache[vertex] ?: -1//vertices.indexOf(vertex)
|
||||||
|
return if (index > 0) {
|
||||||
|
index
|
||||||
|
} else {
|
||||||
|
vertices.add(vertex)
|
||||||
|
vertexCache[vertex] = vertices.size - 1
|
||||||
|
vertices.size - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun face(vertex1: Point3D, vertex2: Point3D, vertex3: Point3D, normal: Point3D?, meta: Meta) {
|
||||||
|
//adding vertices
|
||||||
|
val face = Face3(append(vertex1), append(vertex2), append(vertex3))
|
||||||
|
faces.add(face)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun build(): Mesh {
|
||||||
|
val mesh = TriangleMesh(VertexFormat.POINT_TEXCOORD)
|
||||||
|
vertices.forEach {
|
||||||
|
//TODO optimize copy
|
||||||
|
mesh.points.addAll(it.x.toFloat(), it.y.toFloat(), it.z.toFloat())
|
||||||
|
}
|
||||||
|
|
||||||
|
mesh.texCoords.addAll(0f, 0f)
|
||||||
|
|
||||||
|
faces.forEach {
|
||||||
|
mesh.faces.addAll(it.p0, 0, it.p1, 0, it.p2, 0)
|
||||||
|
}
|
||||||
|
return mesh
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user