Large API update

This commit is contained in:
Alexander Nozik 2019-12-22 20:56:19 +03:00
parent 0164534004
commit 8189012b70
51 changed files with 894 additions and 906 deletions

View File

@ -1,3 +1,5 @@
[![JetBrains Research](https://jb.gg/badges/research.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub)
# DataForge plugins for visualisation # DataForge plugins for visualisation
## Common visualisation objects ## Common visualisation objects

View File

@ -1,8 +1,10 @@
import scientifik.useSerialization
val dataforgeVersion by extra("0.1.5-dev-4") val dataforgeVersion by extra("0.1.5-dev-4")
plugins { plugins {
val kotlinVersion = "1.3.61" val kotlinVersion = "1.3.61"
val toolsVersion = "0.2.7" val toolsVersion = "0.3.1"
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
@ -27,6 +29,10 @@ allprojects {
version = "0.1.0-dev" version = "0.1.0-dev"
} }
subprojects{
useSerialization("0.13.0")
}
val githubProject by extra("dataforge-vis") val githubProject by extra("dataforge-vis")
val bintrayRepo by extra("dataforge") val bintrayRepo by extra("dataforge")

View File

@ -5,10 +5,6 @@ plugins {
id("org.openjfx.javafxplugin") 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") //val kvisionVersion: String by rootProject.extra("2.0.0-M1")

View File

@ -1,8 +1,10 @@
package hep.dataforge.vis.common package hep.dataforge.vis.common
import hep.dataforge.meta.Meta
import hep.dataforge.meta.MetaItem import hep.dataforge.meta.MetaItem
import hep.dataforge.names.* import hep.dataforge.names.Name
import hep.dataforge.names.NameToken
import hep.dataforge.names.asName
import hep.dataforge.names.isEmpty
import kotlinx.serialization.Transient import kotlinx.serialization.Transient
@ -16,46 +18,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)

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,15 +16,25 @@ 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)
}
}
/** /**
* The config is initialized and assigned on-demand. * The config is initialized and assigned on-demand.
* To avoid unnecessary allocations, one should access [properties] via [getProperty] instead. * To avoid unnecessary allocations, one should access [properties] via [getProperty] instead.
@ -67,14 +76,6 @@ abstract class AbstractVisualObject : VisualObject {
} }
/**
* 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) {
properties?.get(name) ?: mergedStyles[name] ?: parent?.getProperty(name, inherit) properties?.get(name) ?: mergedStyles[name] ?: parent?.getProperty(name, inherit)
@ -84,10 +85,10 @@ abstract class AbstractVisualObject : VisualObject {
} }
} }
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,10 @@
package hep.dataforge.vis.common package hep.dataforge.vis.common
import hep.dataforge.meta.*
import hep.dataforge.values.ValueType
import hep.dataforge.values.int
import kotlin.math.max
/** /**
* Taken from https://github.com/markaren/three.kt/blob/master/threejs-wrapper/src/main/kotlin/info/laht/threekt/math/ColorConstants.kt * Taken from https://github.com/markaren/three.kt/blob/master/threejs-wrapper/src/main/kotlin/info/laht/threekt/math/ColorConstants.kt
*/ */
@ -175,9 +180,38 @@ 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"
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
}
}
}
}
fun rgbToString(rgb: Int): String {
val string = rgb.toString(16).padStart(6, '0')
return "#" + string.substring(max(0, string.length - 6))
}
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("#")
@ -186,4 +220,10 @@ object Colors {
append(colorToString(blue)) append(colorToString(blue))
} }
} }
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,52 @@
@file:UseSerializers(MetaSerializer::class)
package hep.dataforge.vis.common
import hep.dataforge.io.serialization.MetaSerializer
import hep.dataforge.meta.Meta
import hep.dataforge.meta.get
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)
}
operator fun set(key: String, style: Meta?) {
val oldStyle = styleMap[key]
if (style == null) {
styleMap.remove(key)
} else {
styleMap[key] = style
}
owner?.styleChanged(key, oldStyle, style)
}
}
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,8 @@
package hep.dataforge.vis.common package hep.dataforge.vis.common
import hep.dataforge.meta.Meta import hep.dataforge.meta.MetaBuilder
import hep.dataforge.meta.buildMeta
import hep.dataforge.meta.seal
import hep.dataforge.names.* import hep.dataforge.names.*
import hep.dataforge.provider.Provider import hep.dataforge.provider.Provider
@ -12,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) ->
@ -22,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() }
else -> emptyMap() else -> emptyMap()
} }
@ -32,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
@ -50,8 +43,30 @@ 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"
}
} }
fun VisualGroup.updateStyle(key: String, builder: MetaBuilder.() -> Unit) {
val newStyle = styleSheet[key]?.let { buildMeta(it, builder) } ?: buildMeta(builder)
styleSheet[key] = newStyle.seal()
}
data class StyleRef(val group: VisualGroup, val styleName: Name)
val VisualGroup.isEmpty: Boolean get() = this.children.isEmpty() val VisualGroup.isEmpty: Boolean get() = this.children.isEmpty()
interface MutableVisualGroup : VisualGroup { interface MutableVisualGroup : VisualGroup {

View File

@ -1,9 +1,6 @@
package hep.dataforge.vis.common package hep.dataforge.vis.common
import hep.dataforge.meta.Config import hep.dataforge.meta.*
import hep.dataforge.meta.Configurable
import hep.dataforge.meta.Laminate
import hep.dataforge.meta.MetaItem
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.names.asName import hep.dataforge.names.asName
import hep.dataforge.names.toName import hep.dataforge.names.toName
@ -26,11 +23,6 @@ interface VisualObject : Configurable {
@Transient @Transient
var parent: VisualObject? var parent: VisualObject?
/**
* Direct properties access
*/
val properties: Config?
/** /**
* Set property for this object * Set property for this object
*/ */
@ -42,9 +34,11 @@ interface VisualObject : 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
@ -57,11 +51,9 @@ interface VisualObject : Configurable {
fun removeChangeListener(owner: Any?) fun removeChangeListener(owner: Any?)
/** /**
* List of names of styles applied to this object. Order matters. * 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"
@ -69,12 +61,42 @@ interface VisualObject : Configurable {
//const val META_KEY = "@meta" //const val META_KEY = "@meta"
//const val TAGS_KEY = "@tags" //const val TAGS_KEY = "@tags"
} }
} }
fun VisualObject.getProperty(key: String, inherit: Boolean = true): MetaItem<*>? = getProperty(key.toName(), inherit) fun VisualObject.getProperty(key: String, inherit: Boolean = true): MetaItem<*>? = getProperty(key.toName(), inherit)
fun VisualObject.setProperty(key: String, value: Any?) = setProperty(key.toName(), value) fun VisualObject.setProperty(key: String, value: Any?) = setProperty(key.toName(), value)
fun VisualObject.applyStyle(name: String) { /**
styles = styles + name.toName() * 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.useStyle(name: String) {
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

@ -1,32 +1,20 @@
import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpack
plugins { plugins {
id("scientifik.mpp") id("scientifik.mpp")
} }
scientifik{
withSerialization()
}
kotlin { kotlin {
sourceSets { sourceSets {
val commonMain by getting { val commonMain by getting {
dependencies { dependencies {
api(project(":dataforge-vis-spatial")) api(project(":dataforge-vis-spatial"))
api("scientifik:gdml:0.1.3") 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{ //tasks{
val jsBrowserWebpack by getting(KotlinWebpack::class) { // val jsBrowserWebpack by getting(KotlinWebpack::class) {
sourceMaps = false // sourceMaps = false
} // }
} //}

View File

@ -6,8 +6,8 @@ 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.VisualObject import hep.dataforge.vis.common.VisualObject
import hep.dataforge.vis.common.applyStyle import hep.dataforge.vis.common.useStyle
import hep.dataforge.vis.spatial.Material3D.Companion.COLOR_KEY import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_COLOR_KEY
import hep.dataforge.vis.spatial.RotationOrder import hep.dataforge.vis.spatial.RotationOrder
import hep.dataforge.vis.spatial.VisualGroup3D import hep.dataforge.vis.spatial.VisualGroup3D
import hep.dataforge.vis.spatial.VisualObject3D import hep.dataforge.vis.spatial.VisualObject3D
@ -43,7 +43,7 @@ class GDMLTransformer(val root: GDML) {
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,7 +52,7 @@ class GDMLTransformer(val root: GDML) {
val styleName = "material[${material.name}]" val styleName = "material[${material.name}]"
obj.useStyle(styleName){ obj.useStyle(styleName){
COLOR_KEY to random.nextInt(0, Int.MAX_VALUE) MATERIAL_COLOR_KEY put random.nextInt(16777216)
"gdml.material" put material.name "gdml.material" put material.name
} }
@ -68,7 +68,7 @@ class GDMLTransformer(val root: GDML) {
internal fun finalize(final: VisualGroup3D): VisualGroup3D { internal fun finalize(final: VisualGroup3D): VisualGroup3D {
final.prototypes = proto final.prototypes = proto
styleCache.forEach { styleCache.forEach {
final.addStyle(it.key, it.value, false) final.styleSheet[it.key.toString()] = it.value
} }
final.rotationOrder = RotationOrder.ZXY final.rotationOrder = RotationOrder.ZXY
onFinish(this@GDMLTransformer) onFinish(this@GDMLTransformer)

View File

@ -244,4 +244,11 @@ fun GDML.toVisual(block: GDMLTransformer.() -> Unit = {}): VisualGroup3D {
val context = GDMLTransformer(this).apply(block) val context = GDMLTransformer(this).apply(block)
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 = {}) {
set(key, gdml.toVisual(transformer))
} }

View File

@ -5,11 +5,10 @@ import hep.dataforge.js.Application
import hep.dataforge.js.objectTree import hep.dataforge.js.objectTree
import hep.dataforge.js.startApplication import hep.dataforge.js.startApplication
import hep.dataforge.names.NameToken import hep.dataforge.names.NameToken
import hep.dataforge.vis.spatial.Material3D.Companion.OPACITY_KEY import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_OPACITY_KEY
import hep.dataforge.vis.spatial.Visual3DPlugin import hep.dataforge.vis.spatial.Visual3DPlugin
import hep.dataforge.vis.spatial.VisualGroup3D import hep.dataforge.vis.spatial.VisualGroup3D
import hep.dataforge.vis.spatial.VisualObject3D import hep.dataforge.vis.spatial.VisualObject3D
import hep.dataforge.vis.spatial.attachChildren
import hep.dataforge.vis.spatial.editor.propertyEditor import hep.dataforge.vis.spatial.editor.propertyEditor
import hep.dataforge.vis.spatial.editor.threeOutputConfig import hep.dataforge.vis.spatial.editor.threeOutputConfig
import hep.dataforge.vis.spatial.gdml.GDMLTransformer import hep.dataforge.vis.spatial.gdml.GDMLTransformer
@ -106,7 +105,7 @@ private class GDMLDemoApp : Application {
|| parent.physVolumes.isNotEmpty() || parent.physVolumes.isNotEmpty()
) { ) {
useStyle("opaque") { useStyle("opaque") {
OPACITY_KEY to 0.3 MATERIAL_OPACITY_KEY put 0.3
} }
} }
} }
@ -154,7 +153,7 @@ private class GDMLDemoApp : Application {
output.camera.layers.set(0) output.camera.layers.set(0)
configElement.threeOutputConfig(output) configElement.threeOutputConfig(output)
//tree.visualObjectTree(visual, editor::propertyEditor) //tree.visualObjectTree(visual, editor::propertyEditor)
treeElement.objectTree(NameToken("World"),visual, editorElement::propertyEditor) treeElement.objectTree(NameToken("World"), visual, editorElement::propertyEditor)
output.render(visual) output.render(visual)

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,16 +0,0 @@
package hep.dataforge.vis.spatial.gdml
import hep.dataforge.names.toName
import hep.dataforge.vis.spatial.*
fun main() {
val vis = VisualGroup3D().apply {
val box = Box(100f, 100f, 20f).apply {
color(0u, 0u, 255u)
}
proxy("some.name".toName(), box, "obj")
}
val string = Visual3DPlugin.json.stringify(VisualGroup3D.serializer(),vis)
println(string)
}

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,13 @@
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 VisualGroup3D.gdml(file: Path, key: String = "", transformer: GDMLTransformer.() -> Unit = {}) {
val xmlReader = StAXReader(Files.newInputStream(file), "UTF-8")
val gdml = GDML.format.parse(GDML.serializer(), xmlReader)
gdml(gdml, key, transformer)
}

View File

@ -5,10 +5,6 @@ plugins {
id("org.openjfx.javafxplugin") id("org.openjfx.javafxplugin")
} }
scientifik {
withSerialization()
}
kotlin { kotlin {
jvm { jvm {
withJava() withJava()
@ -21,9 +17,13 @@ kotlin {
} }
jvmMain { jvmMain {
dependencies { dependencies {
api("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}") api("org.jetbrains.kotlinx:kotlinx-coroutines-javafx:${Scientifik.coroutinesVersion}")
implementation("eu.mihosoft.vrl.jcsg:jcsg:0.5.7") implementation("eu.mihosoft.vrl.jcsg:jcsg:0.5.7") {
exclude(module = "slf4j-simple")
}
} }
} }
jsMain { jsMain {

View File

@ -1,4 +1,5 @@
@file:UseSerializers(Point3DSerializer::class) @file:UseSerializers(Point3DSerializer::class)
package hep.dataforge.vis.spatial package hep.dataforge.vis.spatial
import hep.dataforge.io.serialization.ConfigSerializer import hep.dataforge.io.serialization.ConfigSerializer
@ -43,10 +44,9 @@ 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
}
it.position = group.position it.position = group.position
it.rotation = group.rotation it.rotation = group.rotation
it.scale = group.scale it.scale = group.scale

View File

@ -9,7 +9,8 @@ import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers import kotlinx.serialization.UseSerializers
@Serializable @Serializable
class Text3D(var text: String, var fontSize: Int) : AbstractVisualObject(), VisualObject3D { class Label3D(var text: String, var fontSize: Double, var fontFamily: String) : AbstractVisualObject(),
VisualObject3D {
@Serializable(ConfigSerializer::class) @Serializable(ConfigSerializer::class)
override var properties: Config? = null override var properties: Config? = null
@ -19,5 +20,11 @@ class Text3D(var text: String, var fontSize: Int) : AbstractVisualObject(), Visu
} }
fun VisualGroup3D.text(text: String, fontSize: Int, name: String = "", action: Text3D.() -> Unit = {}) = fun VisualGroup3D.label(
Text3D(text, fontSize).apply(action).also { set(name, it) } 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

@ -3,63 +3,72 @@ package hep.dataforge.vis.spatial
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.vis.common.VisualObject import hep.dataforge.vis.common.Colors
import hep.dataforge.vis.spatial.Material3D.Companion.COLOR_KEY import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_COLOR_KEY
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_KEY import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_OPACITY_KEY
import hep.dataforge.vis.spatial.Material3D.Companion.OPACITY_KEY
class Material3D(override val config: Config) : Specific { class Material3D(override val config: Config) : Specific {
var color by string() var color by string(key = COLOR_KEY)
var 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 SPECULAR_COLOR = MATERIAL_KEY + "specularColor" val MATERIAL_COLOR_KEY = MATERIAL_KEY + COLOR_KEY
val OPACITY_KEY = MATERIAL_KEY + "opacity" 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
} }
} }
fun VisualObject.color(rgb: String) { fun VisualObject3D.color(rgb: String) {
setProperty(COLOR_KEY, rgb) setProperty(MATERIAL_COLOR_KEY, rgb)
} }
fun VisualObject.color(rgb: Int) { fun VisualObject3D.color(rgb: Int) {
setProperty(COLOR_KEY, rgb) setProperty(MATERIAL_COLOR_KEY, rgb)
} }
fun VisualObject.color(r: UByte, g: UByte, b: UByte) = setProperty( fun VisualObject3D.color(r: UByte, g: UByte, b: UByte) = setProperty(
COLOR_KEY, MATERIAL_COLOR_KEY,
buildMeta { Colors.rgbToMeta(r, g, b)
"red" put r.toInt()
"green" put g.toInt()
"blue" put b.toInt()
}
) )
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.build(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

@ -5,17 +5,13 @@ package hep.dataforge.vis.spatial
import hep.dataforge.io.serialization.ConfigSerializer import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.io.serialization.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.MetaItem import hep.dataforge.meta.MetaItem
import hep.dataforge.meta.get 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 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.Transient
import kotlinx.serialization.UseSerializers import kotlinx.serialization.UseSerializers
@ -43,12 +39,8 @@ class Proxy(val templateName: Name) : AbstractVisualObject(), VisualGroup, Visua
get() = (parent as? VisualGroup3D)?.getPrototype(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) {
@ -77,16 +69,17 @@ class Proxy(val templateName: Name) : AbstractVisualObject(), VisualGroup, Visua
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> override var styles: List<String>
get() = super.styles + prototype.styles get() = super.styles + prototype.styles
set(value) { set(value) {
setProperty(VisualObject.STYLE_KEY, value.map { it.toString() }) setProperty(VisualObject.STYLE_KEY, value)
styleChanged() updateStyles(value)
} }
//override fun findAllStyles(): Laminate = Laminate((styles + prototype.styles).mapNotNull { findStyle(it) }) //override fun findAllStyles(): Laminate = Laminate((styles + prototype.styles).mapNotNull { findStyle(it) })
@ -94,9 +87,9 @@ class Proxy(val templateName: Name) : AbstractVisualObject(), VisualGroup, Visua
@Serializable @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, _) ->
@ -105,12 +98,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) {
@ -176,8 +163,8 @@ fun VisualGroup3D.proxy(
): Proxy { ): Proxy {
val existing = getPrototype(templateName) val existing = getPrototype(templateName)
if (existing == null) { if (existing == null) {
setPrototype(templateName,obj, attachToParent) setPrototype(templateName, obj, attachToParent)
} else if(existing != obj) { } else if (existing != obj) {
error("Can't add different prototype on top of existing one") error("Can't add different prototype on top of existing one")
} }
return ref(templateName, name, block) return ref(templateName, name, block)

View File

@ -12,12 +12,12 @@ import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.io.serialization.MetaSerializer import hep.dataforge.io.serialization.MetaSerializer
import hep.dataforge.io.serialization.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.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.StyleSheet
import hep.dataforge.vis.common.VisualGroup import hep.dataforge.vis.common.VisualGroup
import hep.dataforge.vis.common.VisualObject import hep.dataforge.vis.common.VisualObject
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
@ -37,9 +37,10 @@ class VisualGroup3D : AbstractVisualGroup(), VisualObject3D {
field = value field = value
} }
override val styleSheet: StyleSheet = StyleSheet(this)
//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
@ -49,6 +50,11 @@ 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()
}
override fun removeChild(token: NameToken) { override fun removeChild(token: NameToken) {
_children.remove(token) _children.remove(token)
childrenChanged(token.asName(), null) childrenChanged(token.asName(), null)
@ -96,26 +102,18 @@ class VisualGroup3D : AbstractVisualGroup(), VisualObject3D {
} }
} }
override fun attachChildren() {
super.attachChildren()
prototypes?.run {
parent = this
attachChildren()
}
}
companion object { companion object {
const val PROTOTYPES_KEY = "templates" const val PROTOTYPES_KEY = "templates"
} }
} }
/**
* A fix for serialization bug that writes all proper parents inside the tree after deserialization
*/
fun VisualGroup.attachChildren() {
this.children.values.forEach {
it.parent = this
(it as? VisualGroup)?.attachChildren()
}
if (this is VisualGroup3D) {
prototypes?.also {
it.parent = this
it.attachChildren()
}
}
}
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) }

View File

@ -129,21 +129,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 =
@ -153,21 +153,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 =
@ -177,19 +177,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

@ -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)
@ -53,7 +53,7 @@ fun Meta.point3D() = Point3D(this["x"].number ?: 0, this["y"].number ?: 0, this[
val zero = Point3D(0, 0, 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

@ -10,7 +10,9 @@ 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 += parent.position

View File

@ -0,0 +1,55 @@
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.updateStyle
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 {
updateStyle("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 {
updateStyle("testStyle"){
Material3D.MATERIAL_COLOR_KEY put "#555555"
}
group {
box = box(100,100,100){
useStyle("testStyle")
}
}
}
assertEquals("#555555", box?.color)
}
}

View File

@ -5,13 +5,10 @@ import hep.dataforge.js.jsObject
import hep.dataforge.meta.* import hep.dataforge.meta.*
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.spatial.Material3D.Companion.COLOR_KEY import hep.dataforge.vis.spatial.*
import hep.dataforge.vis.spatial.Material3D.Companion.OPACITY_KEY import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_COLOR_KEY
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_OPACITY_KEY
import hep.dataforge.vis.spatial.VisualObject3D.Companion.VISIBLE_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
@ -27,11 +24,17 @@ fun Element.propertyEditor(item: VisualObject?) {
if (item != null) { if (item != null) {
append { append {
card("Properties") { card("Properties") {
val config = (item.properties ?: item.prototype?.properties) ?: EmptyMeta val config: Meta = if (item is Proxy || item is Proxy.ProxyChild) {
item.prototype?.config ?: EmptyMeta
} else {
item.config
}
val metaToEdit = config.builder().apply { val metaToEdit = config.builder().apply {
VISIBLE_KEY to (item.visible ?: true) VISIBLE_KEY to (item.visible ?: true)
COLOR_KEY to (item.color ?: "#ffffff") if (item is VisualObject3D) {
OPACITY_KEY to (item.opacity ?: 1.0) MATERIAL_COLOR_KEY to (item.color ?: "#ffffff")
MATERIAL_OPACITY_KEY to (item.opacity ?: 1.0)
}
} }
val dMeta: dynamic = metaToEdit.toDynamic() val dMeta: dynamic = metaToEdit.toDynamic()
val options: JSONEditorOptions = jsObject { val options: JSONEditorOptions = jsObject {

View File

@ -35,7 +35,7 @@ abstract class MeshThreeFactory<in T : VisualObject3D>(
//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 meshMeta: Meta = obj.properties[Material3D.MATERIAL_KEY]?.node ?: Meta.empty //val meshMeta: Meta = obj.properties[Material3D.MATERIAL_KEY]?.node ?: Meta.empty
val mesh = Mesh(geometry, MeshBasicMaterial()).apply { val mesh = Mesh(geometry, MeshBasicMaterial()).apply {
matrixAutoUpdate = false matrixAutoUpdate = false

View File

@ -11,7 +11,6 @@ import info.laht.threekt.materials.MeshBasicMaterial
import info.laht.threekt.materials.MeshPhongMaterial import info.laht.threekt.materials.MeshPhongMaterial
import info.laht.threekt.math.Color import info.laht.threekt.math.Color
import info.laht.threekt.objects.Mesh import info.laht.threekt.objects.Mesh
import kotlin.math.max
object ThreeMaterials { object ThreeMaterials {
@ -46,12 +45,6 @@ object ThreeMaterials {
linewidth = meta["thickness"].double ?: 1.0 linewidth = meta["thickness"].double ?: 1.0
} }
} }
fun rgbToString(rgb: Int): String {
val string = rgb.toString(16).padStart(6, '0')
return "#" + string.substring(max(0, string.length - 6))
}
} }
/** /**
@ -67,9 +60,9 @@ fun MetaItem<*>.color(): Color {
} }
is MetaItem.NodeItem -> { is MetaItem.NodeItem -> {
Color( Color(
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
) )
} }
} }
@ -99,11 +92,12 @@ fun MetaItem<*>.color(): Color {
//fun Material3D?.jsLineMaterial(): Material = this?.config.jsLineMaterial() //fun Material3D?.jsLineMaterial(): Material = this?.config.jsLineMaterial()
fun Mesh.updateMaterial(obj: VisualObject) { fun Mesh.updateMaterial(obj: VisualObject) {
val meta = obj.properties[Material3D.MATERIAL_KEY].node?:EmptyMeta val meta = obj.getProperty(Material3D.MATERIAL_KEY).node?:EmptyMeta
material = (material as? MeshBasicMaterial ?: MeshBasicMaterial()).apply { material = (material as? MeshBasicMaterial ?: MeshBasicMaterial()).apply {
color = meta["color"]?.color() ?: ThreeMaterials.DEFAULT_COLOR color = meta[Material3D.COLOR_KEY]?.color() ?: ThreeMaterials.DEFAULT_COLOR
opacity = meta["opacity"]?.double ?: 1.0 opacity = meta[Material3D.OPACITY_KEY]?.double ?: 1.0
transparent = meta["transparent"].boolean ?: (opacity < 1.0) transparent = opacity < 1.0
wireframe = meta[Material3D.WIREFRAME_KEY].boolean?:false
needsUpdate = true needsUpdate = true
} }
} }

View File

@ -27,6 +27,7 @@ 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] = ThreeTextFactory
} }
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")

View File

@ -0,0 +1,47 @@
package hep.dataforge.vis.spatial.three
import hep.dataforge.vis.spatial.Label3D
import info.laht.threekt.DoubleSide
import info.laht.threekt.core.Object3D
import info.laht.threekt.geometries.PlaneGeometry
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.HTMLCanvasElement
import kotlin.browser.document
import kotlin.reflect.KClass
/**
* Using example from http://stemkoski.github.io/Three.js/Texture-From-Canvas.html
*/
object ThreeTextFactory : 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 = "${obj.fontSize}pt ${obj.fontFamily}"
context.fillStyle = "rgba(255,0,0,0.95)"//obj.material?.color ?: "black"
context.fillText(obj.text, 0.0, 0.0)
// canvas contents will be used for a texture
val texture = Texture(canvas)
texture.needsUpdate = true
val material = MeshBasicMaterial().apply {
map = texture
side = DoubleSide
}
material.transparent = true;
val mesh = Mesh(
PlaneGeometry(canvas.clientWidth, canvas.clientHeight),
material
)
mesh.updatePosition(obj)
return mesh
}
}

View File

@ -1,211 +0,0 @@
package hep.dataforge.vis.spatial.demo
import hep.dataforge.vis.common.Colors
import hep.dataforge.vis.spatial.*
import javafx.stage.Stage
import kotlinx.coroutines.*
import kotlinx.coroutines.javafx.JavaFx
import tornadofx.*
import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.sin
import kotlin.random.Random
class FXDemoApp : App(FXDemoGrid::class) {
val view: FXDemoGrid by inject()
override fun start(stage: Stage) {
super.start(stage)
view.run {
demo("shapes", "Basic shapes") {
box(100.0, 100.0, 100.0) {
z = 110.0
}
sphere(50.0) {
x = 110
detail = 16
}
tube(50, height = 10, innerRadius = 25, angle = PI) {
y = 110
detail = 16
rotationX = PI / 4
}
}
demo("dynamic", "Dynamic properties") {
val group = group {
box(100, 100, 100) {
z = 110.0
}
box(100, 100, 100) {
visible = false
x = 110.0
//override color for this cube
color(1530)
GlobalScope.launch(Dispatchers.JavaFx) {
while (isActive) {
delay(500)
visible = !(visible ?: false)
}
}
}
}
GlobalScope.launch(Dispatchers.JavaFx) {
val random = Random(111)
while (isActive) {
delay(1000)
group.color(random.nextInt(0, Int.MAX_VALUE))
}
}
}
demo("rotation", "Rotations") {
box(100, 100, 100)
group {
x = 200
rotationY = PI / 4
box(100, 100, 100) {
rotationZ = PI / 4
color(Colors.red)
}
}
}
demo("extrude", "extruded shape") {
extrude {
shape {
polygon(8, 50)
}
for (i in 0..100) {
layer(i * 5, 20 * sin(2 * PI / 100 * i), 20 * cos(2 * PI / 100 * i))
}
color(Colors.teal)
}
}
// demo("CSG.simple", "CSG operations") {
// composite(CompositeType.UNION) {
// box(100, 100, 100) {
// z = 50
// }
// sphere(50)
// material {
// color(Colors.lightgreen)
// opacity = 0.3f
// }
// }
// composite(CompositeType.INTERSECT) {
// y = 300
// box(100, 100, 100) {
// z = 50
// }
// sphere(50)
// color(Colors.red)
// }
// composite(CompositeType.SUBTRACT) {
// y = -300
// box(100, 100, 100) {
// z = 50
// }
// sphere(50)
// color(Colors.blue)
// }
// }
// demo("CSG.custom", "CSG with manually created object") {
// intersect {
// box(100, 100, 100)
// tube(60, 10) {
// detail = 180
// }
// }
// }
demo("lines", "Track / line segments") {
sphere(100) {
color(Colors.blue)
detail = 50
opacity = 0.4
}
repeat(20) {
polyline(Point3D(100, 100, 100), Point3D(-100, -100, -100)) {
thickness = 208.0
rotationX = it * PI2 / 20
color(Colors.green)
//rotationY = it * PI2 / 20
}
}
}
// demo("dynamicBox", "Dancing boxes") {
// val boxes = (-10..10).flatMap { i ->
// (-10..10).map { j ->
// varBox(10, 10, 0, name = "cell_${i}_${j}") {
// x = i * 10
// y = j * 10
// value = 128
// setProperty(EDGES_ENABLED_KEY, false)
// setProperty(WIREFRAME_ENABLED_KEY, false)
// }
// }
// }
// GlobalScope.launch {
// while (isActive) {
// delay(200)
// boxes.forEach { box ->
// box.value = (box.value + Random.nextInt(-15, 15)).coerceIn(0..255)
// }
// }
// }
// }
}
}
}
//class SpatialDemoView : View() {
// private val plugin = Global.plugins.fetch(FX3DPlugin)
// private val canvas = FXCanvas3D(plugin)
//
// override val root: Parent = borderpane {
// center = canvas.root
// }
//
// lateinit var group: VisualGroup3D
//
// init {
// canvas.render {
// group = group {
// box(100, 100, 100)
// box(100, 100, 100) {
// x = 110.0
// color(Colors.blue)
// }
// }
// }
//
// //var color by group.config.number(1530)
//
// GlobalScope.launch {
// val random = Random(111)
// while (isActive) {
// delay(1000)
// group.color(random.nextInt(0, Int.MAX_VALUE))
// }
// }
//
//// canvas.apply {
//// angleY = -30.0
//// angleX = -15.0
//// }
// }
//}
fun main() {
launch<FXDemoApp>()
}

View File

@ -2,13 +2,20 @@ package hep.dataforge.vis.spatial.fx
import hep.dataforge.context.* import hep.dataforge.context.*
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.meta.boolean
import hep.dataforge.meta.get import hep.dataforge.meta.get
import hep.dataforge.provider.Type import hep.dataforge.provider.Type
import hep.dataforge.vis.spatial.* 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 hep.dataforge.vis.spatial.fx.FX3DFactory.Companion.TYPE
import javafx.scene.Group import javafx.scene.Group
import javafx.scene.Node import javafx.scene.Node
import javafx.scene.shape.CullFace
import javafx.scene.shape.DrawMode
import javafx.scene.shape.Shape3D import javafx.scene.shape.Shape3D
import javafx.scene.text.Font
import javafx.scene.text.Text
import javafx.scene.transform.Rotate import javafx.scene.transform.Rotate
import org.fxyz3d.shapes.composites.PolyLine3D import org.fxyz3d.shapes.composites.PolyLine3D
import org.fxyz3d.shapes.primitives.CuboidMesh import org.fxyz3d.shapes.primitives.CuboidMesh
@ -56,11 +63,18 @@ class FX3DPlugin : AbstractPlugin() {
} else { } else {
FXShapeFactory(obj, binding) 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( is PolyLine -> PolyLine3D(
obj.points.map { it.point }, obj.points.map { it.point },
obj.thickness.toFloat(), obj.thickness.toFloat(),
obj.material?.get("color")?.color() obj.getProperty(Material3D.MATERIAL_COLOR_KEY)?.color()
) ).apply {
this.meshView.cullFace = CullFace.FRONT
}
else -> { else -> {
//find specialized factory for this type if it is present //find specialized factory for this type if it is present
val factory: FX3DFactory<VisualObject3D>? = findObjectFactory(obj::class) val factory: FX3DFactory<VisualObject3D>? = findObjectFactory(obj::class)
@ -79,15 +93,15 @@ class FX3DPlugin : AbstractPlugin() {
scaleZProperty().bind(binding[VisualObject3D.zScale].float(obj.scaleZ.toFloat())) scaleZProperty().bind(binding[VisualObject3D.zScale].float(obj.scaleZ.toFloat()))
val rotateX = Rotate(0.0, Rotate.X_AXIS).apply { val rotateX = Rotate(0.0, Rotate.X_AXIS).apply {
angleProperty().bind(binding[VisualObject3D.xRotation].float(obj.rotationX.toFloat())) angleProperty().bind(binding[VisualObject3D.xRotation].float(obj.rotationX.toFloat()).multiply(180.0 / PI))
} }
val rotateY = Rotate(0.0, Rotate.Y_AXIS).apply { val rotateY = Rotate(0.0, Rotate.Y_AXIS).apply {
angleProperty().bind(binding[VisualObject3D.yRotation].float(obj.rotationY.toFloat())) angleProperty().bind(binding[VisualObject3D.yRotation].float(obj.rotationY.toFloat()).multiply(180.0 / PI))
} }
val rotateZ = Rotate(0.0, Rotate.Z_AXIS).apply { val rotateZ = Rotate(0.0, Rotate.Z_AXIS).apply {
angleProperty().bind(binding[VisualObject3D.zRotation].float(obj.rotationZ.toFloat())) angleProperty().bind(binding[VisualObject3D.zRotation].float(obj.rotationZ.toFloat()).multiply(180.0 / PI))
} }
when (obj.rotationOrder) { when (obj.rotationOrder) {
@ -100,9 +114,17 @@ class FX3DPlugin : AbstractPlugin() {
} }
if (this is Shape3D) { if (this is Shape3D) {
materialProperty().bind(binding[Material3D.MATERIAL_KEY].transform { materialProperty().bind(binding[MATERIAL_KEY].transform {
it.material() it.material()
}) })
drawModeProperty().bind(binding[MATERIAL_WIREFRAME_KEY].transform {
if (it.boolean == true) {
DrawMode.LINE
} else {
DrawMode.FILL
}
})
} }
} }
} }

View File

@ -33,13 +33,15 @@ class FXCanvas3D(val plugin: FX3DPlugin, meta: Meta = EmptyMeta) :
it.setRadius(meta["axis.width"].double ?: LINE_WIDTH) it.setRadius(meta["axis.width"].double ?: LINE_WIDTH)
it.isVisible = meta["axis.visible"].boolean ?: (meta["axis"] != null) it.isVisible = meta["axis.visible"].boolean ?: (meta["axis"] != null)
world.add(it) world.add(it)
} }
val light = AmbientLight()
private val camera = PerspectiveCamera().apply { private val camera = PerspectiveCamera().apply {
nearClip = CAMERA_NEAR_CLIP nearClip = CAMERA_NEAR_CLIP
farClip = CAMERA_FAR_CLIP farClip = CAMERA_FAR_CLIP
translateZ = CAMERA_INITIAL_DISTANCE translateZ = CAMERA_INITIAL_DISTANCE
this.add(light)
} }
val cameraTransform = CameraTransformer().also { val cameraTransform = CameraTransformer().also {
@ -60,20 +62,27 @@ class FXCanvas3D(val plugin: FX3DPlugin, meta: Meta = EmptyMeta) :
val rotationZProperty get() = cameraTransform.rz.angleProperty() val rotationZProperty get() = cameraTransform.rz.angleProperty()
var angleZ by rotationZProperty var angleZ by rotationZProperty
private val canvas = SubScene(
Group(world, cameraTransform).apply { DepthTest.ENABLE },
400.0,
400.0,
true,
SceneAntialiasing.BALANCED
).also { scene ->
scene.fill = Color.GREY
scene.camera = camera
//id = "canvas"
handleKeyboard(scene)
handleMouse(scene)
}
override val root = borderpane { override val root = borderpane {
center = SubScene( center = canvas
Group(world, cameraTransform).apply { DepthTest.ENABLE }, }
1024.0,
768.0, init {
true, canvas.widthProperty().bind(root.widthProperty())
SceneAntialiasing.BALANCED canvas.heightProperty().bind(root.heightProperty())
).also { scene ->
scene.fill = Color.GREY
scene.camera = camera
id = "canvas"
handleKeyboard(scene)
handleMouse(scene)
}
} }
@ -170,6 +179,6 @@ class FXCanvas3D(val plugin: FX3DPlugin, meta: Meta = EmptyMeta) :
private const val ROTATION_SPEED = 2.0 private const val ROTATION_SPEED = 2.0
private const val TRACK_SPEED = 6.0 private const val TRACK_SPEED = 6.0
private const val RESIZE_SPEED = 50.0 private const val RESIZE_SPEED = 50.0
private const val LINE_WIDTH = 3.0 private const val LINE_WIDTH = 1.0
} }
} }

View File

@ -1,25 +1,57 @@
package hep.dataforge.vis.spatial.fx package hep.dataforge.vis.spatial.fx
import eu.mihosoft.jcsg.CSG 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.Composite
import hep.dataforge.vis.spatial.CompositeType import hep.dataforge.vis.spatial.CompositeType
import javafx.scene.Group import javafx.scene.Group
import javafx.scene.Node import javafx.scene.Node
import javafx.scene.shape.MeshView import javafx.scene.shape.MeshView
import org.fxyz3d.utils.MeshUtils import javafx.scene.shape.TriangleMesh
import javafx.scene.shape.VertexFormat
import java.util.*
import kotlin.collections.HashMap
import kotlin.reflect.KClass import kotlin.reflect.KClass
class FXCompositeFactory(val plugin: FX3DPlugin) : private fun MeshView.toCSG(): CSG {
FX3DFactory<Composite> { 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> override val type: KClass<in Composite>
get() = Composite::class get() = Composite::class
override fun invoke(obj: Composite, binding: VisualObjectFXBinding): Node { override fun invoke(obj: Composite, binding: VisualObjectFXBinding): Node {
val first = plugin.buildNode(obj.first) as? MeshView ?: error("Can't build 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 second = plugin.buildNode(obj.second) as? MeshView ?: error("Can't build node")
val firstCSG = MeshUtils.mesh2CSG(first) val firstCSG = first.toCSG()
val secondCSG = MeshUtils.mesh2CSG(second) val secondCSG = second.toCSG()
val resultCSG = when(obj.compositeType){ val resultCSG = when (obj.compositeType) {
CompositeType.UNION -> firstCSG.union(secondCSG) CompositeType.UNION -> firstCSG.union(secondCSG)
CompositeType.INTERSECT -> firstCSG.intersect(secondCSG) CompositeType.INTERSECT -> firstCSG.intersect(secondCSG)
CompositeType.SUBTRACT -> firstCSG.difference(secondCSG) CompositeType.SUBTRACT -> firstCSG.difference(secondCSG)
@ -28,9 +60,9 @@ class FXCompositeFactory(val plugin: FX3DPlugin) :
} }
} }
internal fun CSG.toNode(): Node{ internal fun CSG.toNode(): Node {
val meshes = toJavaFXMesh().asMeshViews val meshes = toJavaFXMesh().asMeshViews
return if(meshes.size == 1){ return if (meshes.size == 1) {
meshes.first() meshes.first()
} else { } else {
Group(meshes.map { it }) Group(meshes.map { it })

View File

@ -5,6 +5,8 @@ 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
@ -39,16 +41,16 @@ fun MetaItem<*>.color(opacity: Double = 1.0): Color {
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 { } else {
Color.web(this.value.string) 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 ?: opacity node[Material3D.OPACITY_KEY]?.double ?: opacity
) )
} }
} }
@ -62,9 +64,9 @@ fun MetaItem<*>?.material(): Material {
null -> FXMaterials.GREY null -> FXMaterials.GREY
is MetaItem.ValueItem -> PhongMaterial(color()) is MetaItem.ValueItem -> PhongMaterial(color())
is MetaItem.NodeItem -> PhongMaterial().apply { is MetaItem.NodeItem -> PhongMaterial().apply {
val opacity = node["opacity"].double ?: 1.0 val opacity = node[Material3D.OPACITY_KEY].double ?: 1.0
diffuseColor = node["color"]?.color(opacity) ?: Color.DARKGREY diffuseColor = node[Material3D.COLOR_KEY]?.color(opacity) ?: Color.DARKGREY
specularColor = node["specularColor"]?.color(opacity) ?: Color.WHITE specularColor = node[Material3D.SPECULAR_COLOR]?.color(opacity) ?: Color.WHITE
} }
} }
} }

View File

@ -7,6 +7,7 @@ import hep.dataforge.vis.spatial.Shape
import javafx.scene.shape.Mesh import javafx.scene.shape.Mesh
import javafx.scene.shape.MeshView import javafx.scene.shape.MeshView
import javafx.scene.shape.TriangleMesh import javafx.scene.shape.TriangleMesh
import javafx.scene.shape.VertexFormat
import org.fxyz3d.geometry.Face3 import org.fxyz3d.geometry.Face3
import kotlin.reflect.KClass import kotlin.reflect.KClass
@ -42,13 +43,16 @@ private class FXGeometryBuilder : GeometryBuilder<Mesh> {
} }
override fun build(): Mesh { override fun build(): Mesh {
val mesh = TriangleMesh() val mesh = TriangleMesh(VertexFormat.POINT_TEXCOORD)
vertices.forEach { vertices.forEach {
//TODO optimize copy //TODO optimize copy
mesh.points.addAll(it.x.toFloat(), it.y.toFloat(), it.z.toFloat()) mesh.points.addAll(it.x.toFloat(), it.y.toFloat(), it.z.toFloat())
} }
mesh.texCoords.addAll(0f, 0f)
faces.forEach { faces.forEach {
mesh.faces.addAll(it.p0, it.p1, it.p2) mesh.faces.addAll(it.p0, 0, it.p1, 0, it.p2, 0)
} }
return mesh return mesh
} }

View File

@ -2,9 +2,10 @@ package hep.dataforge.vis.spatial.fx
import hep.dataforge.meta.* import hep.dataforge.meta.*
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.names.isEmpty import hep.dataforge.names.startsWith
import hep.dataforge.names.toName import hep.dataforge.names.toName
import hep.dataforge.vis.common.VisualObject import hep.dataforge.vis.common.VisualObject
import javafx.application.Platform
import javafx.beans.binding.ObjectBinding import javafx.beans.binding.ObjectBinding
import tornadofx.* import tornadofx.*
@ -16,12 +17,17 @@ class VisualObjectFXBinding(val obj: VisualObject) {
init { init {
obj.onPropertyChange(this) { name, _, _ -> obj.onPropertyChange(this) { name, _, _ ->
var currentName = name bindings.filter { it.key.startsWith(name) }.forEach { entry ->
while(!currentName.isEmpty()) { Platform.runLater {
//recursively update all upper level bindings entry.value.invalidate()
bindings[currentName]?.invalidate() }
currentName = currentName.cutLast()
} }
// var currentName = name
// while (!currentName.isEmpty()) {
// //recursively update all upper level bindings
// bindings[currentName]?.invalidate()
// currentName = currentName.cutLast()
// }
} }
} }
@ -46,9 +52,9 @@ fun ObjectBinding<MetaItem<*>?>.long() = objectBinding { it.long }
fun ObjectBinding<MetaItem<*>?>.node() = objectBinding { it.node } fun ObjectBinding<MetaItem<*>?>.node() = objectBinding { it.node }
fun ObjectBinding<MetaItem<*>?>.string(default: String) = stringBinding { it.string ?: default } fun ObjectBinding<MetaItem<*>?>.string(default: String) = stringBinding { it.string ?: default }
fun ObjectBinding<MetaItem<*>?>.double(default: Double) = objectBinding { it.double ?: default } fun ObjectBinding<MetaItem<*>?>.double(default: Double) = doubleBinding { it.double ?: default }
fun ObjectBinding<MetaItem<*>?>.float(default: Float) = objectBinding { it.float ?: default } fun ObjectBinding<MetaItem<*>?>.float(default: Float) = floatBinding { it.float ?: default }
fun ObjectBinding<MetaItem<*>?>.int(default: Int) = objectBinding { it.int ?: default } fun ObjectBinding<MetaItem<*>?>.int(default: Int) = integerBinding { it.int ?: default }
fun ObjectBinding<MetaItem<*>?>.long(default: Long) = objectBinding { it.long ?:default } fun ObjectBinding<MetaItem<*>?>.long(default: Long) = longBinding { it.long ?: default }
fun <T> ObjectBinding<MetaItem<*>?>.transform(transform: (MetaItem<*>) -> T) = objectBinding { it?.let(transform) } fun <T> ObjectBinding<MetaItem<*>?>.transform(transform: (MetaItem<*>) -> T) = objectBinding { it?.let(transform) }

View File

@ -28,10 +28,9 @@ rootProject.name = "dataforge-vis"
include( include(
":dataforge-vis-common", ":dataforge-vis-common",
":wrappers", ":wrappers",
":dataforge-vis-fx",
":dataforge-vis-spatial", ":dataforge-vis-spatial",
":dataforge-vis-spatial-gdml", ":dataforge-vis-spatial-gdml",
":spatial-js-demo" ":spatial-demo"
) )
//if(file("../dataforge-core").exists()) { //if(file("../dataforge-core").exists()) {

View File

@ -0,0 +1,39 @@
import org.openjfx.gradle.JavaFXOptions
plugins {
id("scientifik.mpp")
id("org.openjfx.javafxplugin")
id("application")
}
kotlin {
jvm {
withJava()
}
js {
browser {
webpackTask {
sourceMaps = false
}
}
}
sourceSets {
commonMain {
dependencies {
api(project(":dataforge-vis-spatial"))
api(project(":dataforge-vis-spatial-gdml"))
}
}
}
}
application {
mainClassName = "hep.dataforge.vis.spatial.demo.FXDemoAppKt"
}
configure<JavaFXOptions> {
modules("javafx.controls")
}

View File

@ -0,0 +1,158 @@
package hep.dataforge.vis.spatial.demo
import hep.dataforge.meta.buildMeta
import hep.dataforge.names.toName
import hep.dataforge.output.OutputManager
import hep.dataforge.vis.common.Colors
import hep.dataforge.vis.common.VisualObject
import hep.dataforge.vis.spatial.*
import kotlinx.coroutines.*
import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.sin
import kotlin.random.Random
fun OutputManager.demo(name: String, title: String = name, block: VisualGroup3D.() -> Unit) {
val meta = buildMeta {
"title" put title
}
val output = get(VisualObject::class, name.toName(), meta = meta)
output.render(action = block)
}
fun OutputManager.showcase() {
demo("shapes", "Basic shapes") {
box(100.0, 100.0, 100.0) {
z = 110.0
}
sphere(50.0) {
x = 110
detail = 16
}
tube(50, height = 10, innerRadius = 25, angle = PI) {
y = 110
detail = 16
rotationX = PI / 4
}
}
demo("dynamic", "Dynamic properties") {
val group = group {
box(100, 100, 100) {
z = 110.0
}
box(100, 100, 100) {
visible = false
x = 110.0
//override color for this cube
color(1530)
GlobalScope.launch(Dispatchers.Main) {
while (isActive) {
delay(500)
visible = !(visible ?: false)
}
}
}
}
GlobalScope.launch(Dispatchers.Main) {
val random = Random(111)
while (isActive) {
delay(1000)
group.color(random.nextInt(0, Int.MAX_VALUE))
}
}
}
demo("rotation", "Rotations") {
box(100, 100, 100)
group {
x = 200
rotationY = PI / 4
box(100, 100, 100) {
rotationZ = PI / 4
color(Colors.red)
}
}
}
demo("extrude", "extruded shape") {
extrude {
shape {
polygon(8, 50)
}
for (i in 0..100) {
layer(i * 5, 20 * sin(2 * PI / 100 * i), 20 * cos(2 * PI / 100 * i))
}
color(Colors.teal)
}
}
demo("lines", "Track / line segments") {
sphere(100) {
detail = 32
opacity = 0.4
color(Colors.blue)
}
repeat(20) {
polyline(Point3D(100, 100, 100), Point3D(-100, -100, -100)) {
thickness = 3.0
rotationX = it * PI2 / 20
color(Colors.green)
//rotationY = it * PI2 / 20
}
}
}
demo("text", "Box with a label") {
box(100, 100, 50) {
opacity = 0.3
}
label("Hello, world!",fontSize = 15) {
z = -26
}
}
}
fun OutputManager.showcaseCSG(){
demo("CSG.simple", "CSG operations") {
composite(CompositeType.UNION) {
box(100, 100, 100) {
z = 50
}
sphere(50)
material {
color(Colors.lightgreen)
opacity = 0.3f
}
}
composite(CompositeType.INTERSECT) {
y = 300
box(100, 100, 100) {
z = 50
}
sphere(50)
color(Colors.red)
}
composite(CompositeType.SUBTRACT) {
y = -300
box(100, 100, 100) {
z = 50
}
sphere(50)
color(Colors.blue)
}
}
demo("CSG.custom", "CSG with manually created object") {
intersect {
tube(60, 10) {
detail = 64
}
box(100, 100, 100)
}
}
}

View File

@ -0,0 +1,59 @@
package hep.dataforge.vis.spatial.demo
import hep.dataforge.context.ContextBuilder
import hep.dataforge.context.Global
import hep.dataforge.js.Application
import hep.dataforge.js.startApplication
import hep.dataforge.vis.spatial.three.MeshThreeFactory.Companion.EDGES_ENABLED_KEY
import hep.dataforge.vis.spatial.three.MeshThreeFactory.Companion.WIREFRAME_ENABLED_KEY
import hep.dataforge.vis.spatial.three.ThreePlugin
import hep.dataforge.vis.spatial.x
import hep.dataforge.vis.spatial.y
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlin.browser.document
import kotlin.random.Random
private class ThreeDemoApp : Application {
override fun start(state: Map<String, Any>) {
val element = document.getElementById("canvas") ?: error("Element with id 'canvas' not found on page")
ThreeDemoGrid(element).run {
showcase()
showcaseCSG()
demo("dynamicBox", "Dancing boxes") {
val boxes = (-10..10).flatMap { i ->
(-10..10).map { j ->
varBox(10, 10, 0, name = "cell_${i}_${j}") {
x = i * 10
y = j * 10
value = 128
setProperty(EDGES_ENABLED_KEY, false)
setProperty(WIREFRAME_ENABLED_KEY, false)
}
}
}
GlobalScope.launch {
while (isActive) {
delay(500)
boxes.forEach { box ->
box.value = (box.value + Random.nextInt(-15, 15)).coerceIn(0..255)
}
}
}
}
}
}
override fun dispose() = emptyMap<String, Any>()//mapOf("lines" put presenter.dispose())
}
fun main() {
startApplication(::ThreeDemoApp)
}

View File

@ -1,20 +1,13 @@
package hep.dataforge.vis.spatial.demo package hep.dataforge.vis.spatial.demo
import hep.dataforge.context.AbstractPlugin import hep.dataforge.context.Global
import hep.dataforge.context.Context
import hep.dataforge.context.PluginFactory
import hep.dataforge.context.PluginTag
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.meta.buildMeta
import hep.dataforge.meta.get import hep.dataforge.meta.get
import hep.dataforge.meta.string import hep.dataforge.meta.string
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.names.toName
import hep.dataforge.output.OutputManager import hep.dataforge.output.OutputManager
import hep.dataforge.output.Renderer import hep.dataforge.output.Renderer
import hep.dataforge.vis.common.VisualObject import hep.dataforge.vis.common.VisualObject
import hep.dataforge.vis.spatial.VisualGroup3D
import hep.dataforge.vis.spatial.render
import hep.dataforge.vis.spatial.three.ThreeCanvas import hep.dataforge.vis.spatial.three.ThreeCanvas
import hep.dataforge.vis.spatial.three.ThreePlugin import hep.dataforge.vis.spatial.three.ThreePlugin
import hep.dataforge.vis.spatial.three.output import hep.dataforge.vis.spatial.three.output
@ -25,31 +18,25 @@ import kotlinx.html.hr
import kotlinx.html.id import kotlinx.html.id
import kotlinx.html.js.div import kotlinx.html.js.div
import kotlinx.html.span import kotlinx.html.span
import org.w3c.dom.Element
import kotlin.browser.document import kotlin.browser.document
import kotlin.dom.clear import kotlin.dom.clear
import kotlin.reflect.KClass import kotlin.reflect.KClass
class ThreeDemoGrid(meta: Meta) : AbstractPlugin(meta), OutputManager { class ThreeDemoGrid(element: Element, meta: Meta = Meta.empty) : OutputManager {
override val tag: PluginTag get() = Companion.tag
private val gridRoot = document.create.div("row") private val gridRoot = document.create.div("row")
private val outputs: MutableMap<Name, ThreeCanvas> = HashMap() private val outputs: MutableMap<Name, ThreeCanvas> = HashMap()
init { private val three = Global.plugins.fetch(ThreePlugin)
require(ThreePlugin)
}
override fun attach(context: Context) { init {
super.attach(context)
val elementId = meta["elementID"].string ?: "canvas"
val element = document.getElementById(elementId) ?: error("Element with id $elementId not found on page")
element.clear() element.clear()
element.append(gridRoot) element.append(gridRoot)
} }
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
override fun <T : Any> get(type: KClass<out T>, name: Name, stage: Name, meta: Meta): Renderer<T> { override fun <T : Any> get(type: KClass<out T>, name: Name, stage: Name, meta: Meta): Renderer<T> {
val three = context.plugins.get<ThreePlugin>()!!
return outputs.getOrPut(name) { return outputs.getOrPut(name) {
if (type != VisualObject::class) error("Supports only DisplayObject") if (type != VisualObject::class) error("Supports only DisplayObject")
@ -63,7 +50,7 @@ class ThreeDemoGrid(meta: Meta) : AbstractPlugin(meta), OutputManager {
gridRoot.append { gridRoot.append {
span("border") { span("border") {
div("col-6") { div("col-6") {
div { id = "output-$name" }.also{ div { id = "output-$name" }.also {
output.attach(it) output.attach(it)
} }
hr() hr()
@ -75,20 +62,5 @@ class ThreeDemoGrid(meta: Meta) : AbstractPlugin(meta), OutputManager {
output output
} as Renderer<T> } as Renderer<T>
} }
companion object : PluginFactory<ThreeDemoGrid> {
override val tag: PluginTag = PluginTag(group = "hep.dataforge", name = "vis.js.spatial.demo")
override val type: KClass<out ThreeDemoGrid> = ThreeDemoGrid::class
override fun invoke(meta: Meta,context: Context): ThreeDemoGrid = ThreeDemoGrid(meta)
}
} }
fun ThreeDemoGrid.demo(name: String, title: String = name, block: VisualGroup3D.() -> Unit) {
val meta = buildMeta {
"title" put title
}
val output = get(VisualObject::class, name.toName(), meta = meta)
output.render(action = block)
}

View File

@ -0,0 +1,40 @@
package hep.dataforge.vis.spatial.demo
import hep.dataforge.vis.spatial.Material3D
import hep.dataforge.vis.spatial.gdml.LUnit
import hep.dataforge.vis.spatial.gdml.gdml
import javafx.stage.Stage
import tornadofx.*
import java.nio.file.Paths
class FXDemoApp : App(FXDemoGrid::class) {
val view: FXDemoGrid by inject()
override fun start(stage: Stage) {
super.start(stage)
stage.width = 400.0
stage.height = 400.0
//view.showcase()
view.demo("gdml", "gdml") {
gdml(Paths.get("D:\\Work\\Projects\\gdml.kt\\gdml-source\\cubes.gdml")) {
lUnit = LUnit.CM
solidConfiguration = { parent, solid ->
if (parent.physVolumes.isNotEmpty()) {
useStyle("opaque") {
Material3D.MATERIAL_OPACITY_KEY put 0.3
}
}
}
}
//setProperty(Material3D.MATERIAL_WIREFRAME_KEY, true)
}
}
}
fun main() {
launch<FXDemoApp>()
}

View File

@ -47,12 +47,4 @@ class FXDemoGrid : View(), OutputManager {
} as Renderer<T> } as Renderer<T>
} }
}
fun FXDemoGrid.demo(name: String, title: String = name, block: VisualGroup3D.() -> Unit) {
val meta = buildMeta {
"title" put title
}
val output = get(VisualObject::class, name.toName(), meta = meta)
output.render(action = block)
} }

View File

@ -1,19 +0,0 @@
plugins {
id("scientifik.js")
//id("kotlin-dce-js")
}
dependencies {
api(project(":dataforge-vis-spatial"))
testImplementation(kotlin("test-js"))
}
//kotlin{
// target {
// browser{
// webpackTask {
// sourceMaps = false
// }
// }
// }
//}

View File

@ -1,179 +0,0 @@
package hep.dataforge.vis.spatial.demo
import hep.dataforge.context.ContextBuilder
import hep.dataforge.js.Application
import hep.dataforge.js.startApplication
import hep.dataforge.vis.common.Colors
import hep.dataforge.vis.spatial.*
import hep.dataforge.vis.spatial.three.MeshThreeFactory.Companion.EDGES_ENABLED_KEY
import hep.dataforge.vis.spatial.three.MeshThreeFactory.Companion.WIREFRAME_ENABLED_KEY
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.sin
import kotlin.random.Random
private class ThreeDemoApp : Application {
override fun start(state: Map<String, Any>) {
//TODO replace by optimized builder after dataforge 0.1.3-dev-8
val context = ContextBuilder("three-demo").build()
context.plugins.load(ThreeDemoGrid()).run {
demo("shapes", "Basic shapes") {
box(100.0, 100.0, 100.0) {
z = 110.0
}
sphere(50.0) {
x = 110
detail = 16
}
tube(50, height = 10, innerRadius = 25, angle = PI) {
y = 110
detail = 16
rotationX = PI / 4
}
}
demo("dynamic", "Dynamic properties") {
val group = group {
box(100, 100, 100) {
z = 110.0
}
box(100, 100, 100) {
visible = false
x = 110.0
//override color for this cube
color(1530)
GlobalScope.launch {
while (isActive) {
delay(500)
visible = !(visible ?: false)
}
}
}
}
GlobalScope.launch {
val random = Random(111)
while (isActive) {
delay(1000)
group.color(random.nextInt(0, Int.MAX_VALUE))
}
}
}
demo("rotation", "Rotations") {
box(100, 100, 100)
group {
x = 200
rotationY = PI / 4
box(100, 100, 100) {
rotationZ = PI / 4
color(Colors.red)
}
}
}
demo("extrude", "extruded shape") {
extrude {
shape {
polygon(8, 50)
}
for (i in 0..100) {
layer(i * 5, 20 * sin(2 * PI / 100 * i), 20 * cos(2 * PI / 100 * i))
}
color(Colors.teal)
}
}
demo("CSG.simple", "CSG operations") {
composite(CompositeType.UNION) {
box(100, 100, 100) {
z = 50
}
sphere(50)
material {
color(Colors.lightgreen)
opacity = 0.3f
}
}
composite(CompositeType.INTERSECT) {
y = 300
box(100, 100, 100) {
z = 50
}
sphere(50)
color(Colors.red)
}
composite(CompositeType.SUBTRACT) {
y = -300
box(100, 100, 100) {
z = 50
}
sphere(50)
color(Colors.blue)
}
}
demo("CSG.custom", "CSG with manually created object") {
intersect {
box(100, 100, 100)
tube(60, 10) {
detail = 180
}
}
}
demo("lines", "Track / line segments") {
sphere(100) {
color(Colors.blue)
detail = 50
opacity = 0.4
}
repeat(20) {
polyline(Point3D(100, 100, 100), Point3D(-100, -100, -100)) {
thickness = 208.0
rotationX = it * PI2 / 20
color(Colors.green)
//rotationY = it * PI2 / 20
}
}
}
demo("dynamicBox", "Dancing boxes") {
val boxes = (-10..10).flatMap { i ->
(-10..10).map { j ->
varBox(10, 10, 0, name = "cell_${i}_${j}") {
x = i * 10
y = j * 10
value = 128
setProperty(EDGES_ENABLED_KEY, false)
setProperty(WIREFRAME_ENABLED_KEY, false)
}
}
}
GlobalScope.launch {
while (isActive) {
delay(200)
boxes.forEach { box ->
box.value = (box.value + Random.nextInt(-15, 15)).coerceIn(0..255)
}
}
}
}
}
}
override fun dispose() = emptyMap<String, Any>()//mapOf("lines" put presenter.dispose())
}
fun main() {
startApplication(::ThreeDemoApp)
}