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:
Peter Klimai 2020-01-02 14:33:29 +03:00
commit 6bb6a82b09
148 changed files with 5863 additions and 1864 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
*/ */
@ -190,8 +220,8 @@ object Colors {
* Convert three bytes representing color to string of format #rrggbb * Convert three bytes representing color to string of format #rrggbb
*/ */
fun rgbToString(red: UByte, green: UByte, blue: UByte): String { fun rgbToString(red: UByte, green: UByte, blue: UByte): String {
fun colorToString(color: UByte): String{ fun colorToString(color: UByte): String {
return color.toString(16).padStart(2,'0') return color.toString(16).padStart(2, '0')
} }
return buildString { return buildString {
append("#") append("#")
@ -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()
}
} }

View File

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

View File

@ -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,8 +43,27 @@ 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 }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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);
}

View File

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

View File

@ -1,6 +1,8 @@
package hep.dataforge.vis.fx.values package hep.dataforge.vis.fx.editor
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.names.Name
import hep.dataforge.names.asName
import hep.dataforge.values.Null import hep.dataforge.values.Null
import hep.dataforge.values.Value import hep.dataforge.values.Value
import hep.dataforge.values.asValue import hep.dataforge.values.asValue
@ -39,9 +41,10 @@ class ColorValueChooser : ValueChooserBase<ColorPicker>() {
return node return node
} }
companion object: ValueChooser.Factory{ companion object : ValueChooser.Factory {
override val name: String = "color" override val name: Name = "color".asName()
override fun invoke(meta: Meta): ValueChooser = ColorValueChooser() override fun invoke(meta: Meta): ValueChooser =
ColorValueChooser()
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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) {
@ -52,9 +54,9 @@ 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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 VisualObject.material: Material3D? //var VisualObject3D.material: Material3D?
get() = getProperty(MATERIAL_KEY).node?.let { Material3D.wrap(it) } // get() = getProperty(MATERIAL_KEY).node?.let { Material3D.wrap(it) }
set(value) = setProperty(MATERIAL_KEY, value?.config) // set(value) = setProperty(MATERIAL_KEY, value?.config)
fun VisualObject.material(builder: Material3D.() -> Unit) { fun VisualObject3D.material(builder: Material3D.() -> Unit) {
material = Material3D.build(builder) val node = config[Material3D.MATERIAL_KEY].node
if (node != null) {
Material3D.update(node, builder)
} else {
config[Material3D.MATERIAL_KEY] = Material3D(builder)
}
} }
var VisualObject.opacity: Double? var VisualObject3D.opacity: Double?
get() = getProperty(OPACITY_KEY).double get() = getProperty(MATERIAL_OPACITY_KEY).double
set(value) { set(value) {
setProperty(OPACITY_KEY, value) setProperty(MATERIAL_OPACITY_KEY, value)
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,13 +8,15 @@ import kotlin.test.assertEquals
class SerializationTest { class SerializationTest {
@ImplicitReflectionSerializer @ImplicitReflectionSerializer
@Test @Test
fun testCubeSerialization(){ fun testCubeSerialization() {
val cube = Box(100f,100f,100f).apply{ val cube = Box(100f, 100f, 100f).apply {
color(222) color(222)
x = 100
z = -100
} }
val 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)
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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