Conflicts:
	dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/AbstractVisualObject.kt
This commit is contained in:
Peter Klimai 2019-10-10 21:29:47 +03:00
commit 7f0b08fcc9
45 changed files with 870 additions and 523 deletions

View File

@ -19,6 +19,8 @@ kotlin {
dependencies {
api("hep.dataforge:dataforge-output-html:$dataforgeVersion")
api(npm("text-encoding"))
api("org.jetbrains:kotlin-extensions:1.0.1-pre.83-kotlin-1.3.50")
api(npm("core-js"))
}
}
}

View File

@ -19,14 +19,17 @@ abstract class AbstractVisualGroup : AbstractVisualObject(), MutableVisualGroup
*/
abstract override val children: Map<NameToken, VisualObject> //get() = _children
//TODO replace by custom object with get/set functionality
protected abstract val styles: MutableMap<Name, Meta>
/**
* 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? = styles[name]
override fun getStyle(name: Name): Meta? = styleSheet[name]
override fun setStyle(name: Name, meta: Meta) {
override fun addStyle(name: Name, meta: Meta, apply: Boolean) {
fun VisualObject.applyStyle(name: Name, meta: Meta) {
if (style.contains(name.toString())) {
if (styles.contains(name)) {
//full update
//TODO do a fine grained update
if (this is AbstractVisualObject) {
@ -41,8 +44,10 @@ abstract class AbstractVisualGroup : AbstractVisualObject(), MutableVisualGroup
}
}
}
styles[name] = meta
applyStyle(name, meta)
styleSheet[name] = meta
if (apply) {
applyStyle(name, meta)
}
}

View File

@ -12,9 +12,6 @@ internal data class PropertyListener(
val action: (name: Name, oldItem: MetaItem<*>?, newItem: MetaItem<*>?) -> Unit
)
/**
* Abstract implementation of [VisualObject]
*/
abstract class AbstractVisualObject : VisualObject {
@Transient
@ -22,13 +19,10 @@ abstract class AbstractVisualObject : VisualObject {
abstract override var properties: Config?
/**
* Style(s) of the object
*/
override var style: List<String>
get() = properties?.let { it[STYLE_KEY].stringList } ?: emptyList()
override var styles: List<Name>
get() = properties?.get(STYLE_KEY).stringList.map(String::toName)
set(value) {
setProperty(STYLE_KEY, value)
setProperty(STYLE_KEY, value.map { it.toString() })
styleChanged()
}
@ -67,8 +61,10 @@ abstract class AbstractVisualObject : VisualObject {
/**
* Collect all styles for this object in a laminate
*/
protected val appliedStyles: Meta
get() = styleCache ?: Laminate(style.map { it.toName() }.mapNotNull(::findStyle)).merge().also { styleCache = it }
protected val mergedStyles: Meta
get() = styleCache ?: findAllStyles().merge().also {
styleCache = it
}
/**
@ -81,9 +77,9 @@ abstract class AbstractVisualObject : VisualObject {
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? {
return if (inherit) {
properties?.get(name) ?: appliedStyles[name] ?: parent?.getProperty(name, inherit)
properties?.get(name) ?: mergedStyles[name] ?: parent?.getProperty(name, inherit)
} else {
properties?.get(name) ?: appliedStyles[name]
properties?.get(name) ?: mergedStyles[name]
}
}
@ -96,7 +92,7 @@ abstract class AbstractVisualObject : VisualObject {
}
}
internal fun VisualObject.findStyle(styleName: Name): Meta? {
fun VisualObject.findStyle(styleName: Name): Meta? {
if (this is VisualGroup) {
val style = getStyle(styleName)
if (style != null) return style

View File

@ -44,7 +44,7 @@ interface VisualGroup : Provider, Iterable<VisualObject>, VisualObject {
/**
* Add or replace style with given name
*/
fun setStyle(name: Name, meta: Meta)
fun addStyle(name: Name, meta: Meta, apply: Boolean = true)
operator fun get(name: Name): VisualObject? {
return when {

View File

@ -1,9 +1,6 @@
package hep.dataforge.vis.common
import hep.dataforge.meta.Config
import hep.dataforge.meta.Configurable
import hep.dataforge.meta.MetaItem
import hep.dataforge.meta.MetaRepr
import hep.dataforge.meta.*
import hep.dataforge.names.Name
import hep.dataforge.names.asName
import hep.dataforge.names.toName
@ -56,11 +53,16 @@ interface VisualObject : MetaRepr, Configurable {
*/
fun removeChangeListener(owner: Any?)
var style: List<String>
/**
* List of names of styles applied to this object
*/
var styles: List<Name>
fun findAllStyles(): Laminate = Laminate(styles.distinct().mapNotNull(::findStyle))
companion object {
const val TYPE = "visual"
val STYLE_KEY = "style".asName()
val STYLE_KEY = "@style".asName()
//const val META_KEY = "@meta"
//const val TAGS_KEY = "@tags"
@ -81,5 +83,5 @@ fun VisualObject.setProperty(key: String, value: Any?) = setProperty(key.toName(
* Apply style to [VisualObject] by adding it to the [style] list
*/
fun VisualObject.applyStyle(name: String) {
style = style + name
styles = styles + name.toName()
}

View File

@ -4,6 +4,7 @@ import hep.dataforge.meta.*
import hep.dataforge.names.Name
import hep.dataforge.names.NameToken
import hep.dataforge.names.asName
import hep.dataforge.names.toName
import hep.dataforge.values.Value
import kotlin.jvm.JvmName
import kotlin.properties.ReadOnlyProperty
@ -35,81 +36,79 @@ class VisualObjectDelegate(
}
class VisualObjectDelegateWrapper<T>(
val obj: VisualObject,
val key: Name?,
val default: T,
val inherited: Boolean,
val write: Config.(name: Name, value: T) -> Unit = { name, value -> set(name, value) },
val read: (MetaItem<*>?) -> T?
) : ReadWriteProperty<VisualObject, T> {
) : ReadWriteProperty<Any?, T> {
//private var cachedName: Name? = null
override fun getValue(thisRef: VisualObject, property: KProperty<*>): T {
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
val name = key ?: property.name.asName()
return if (inherited) {
read(thisRef.getProperty(name))
} else {
read(thisRef.config[name])
} ?: default
return read(obj.getProperty(name,inherited))?:default
}
override fun setValue(thisRef: VisualObject, property: KProperty<*>, value: T) {
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
val name = key ?: property.name.asName()
thisRef.config[name] = value
obj.config[name] = value
}
}
fun VisualObject.value(default: Value? = null, key: String? = null, inherited: Boolean = false) =
VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.value }
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.value }
fun VisualObject.string(default: String? = null, key: String? = null, inherited: Boolean = false) =
VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.string }
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.string }
fun VisualObject.boolean(default: Boolean? = null, key: String? = null, inherited: Boolean = false) =
VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.boolean }
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.boolean }
fun VisualObject.number(default: Number? = null, key: String? = null, inherited: Boolean = false) =
VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.number }
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.number }
fun VisualObject.double(default: Double? = null, key: String? = null, inherited: Boolean = false) =
VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.double }
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.double }
fun VisualObject.int(default: Int? = null, key: String? = null, inherited: Boolean = false) =
VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.int }
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.int }
fun VisualObject.node(key: String? = null, inherited: Boolean = true) =
VisualObjectDelegateWrapper(key?.asName(), null, inherited) { it.node }
VisualObjectDelegateWrapper(this, key?.toName(), null, inherited) { it.node }
fun VisualObject.item(key: String? = null, inherited: Boolean = true) =
VisualObjectDelegateWrapper(key?.asName(), null, inherited) { it }
VisualObjectDelegateWrapper(this, key?.toName(), null, inherited) { it }
//fun <T : Configurable> Configurable.spec(spec: Specification<T>, key: String? = null) = ChildConfigDelegate<T>(key) { spec.wrap(this) }
@JvmName("safeString")
fun VisualObject.string(default: String, key: String? = null, inherited: Boolean = false) =
VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.string }
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.string }
@JvmName("safeBoolean")
fun VisualObject.boolean(default: Boolean, key: String? = null, inherited: Boolean = false) =
VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.boolean }
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.boolean }
@JvmName("safeNumber")
fun VisualObject.number(default: Number, key: String? = null, inherited: Boolean = false) =
VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.number }
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.number }
@JvmName("safeDouble")
fun VisualObject.double(default: Double, key: String? = null, inherited: Boolean = false) =
VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.double }
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.double }
@JvmName("safeInt")
fun VisualObject.int(default: Int, key: String? = null, inherited: Boolean = false) =
VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.int }
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.int }
inline fun <reified E : Enum<E>> VisualObject.enum(default: E, key: String? = null, inherited: Boolean = false) =
VisualObjectDelegateWrapper(
this,
key?.let { NameToken(it).asName() },
default,
inherited
@ -121,11 +120,11 @@ fun <T> VisualObject.merge(
key: String? = null,
transformer: (Sequence<MetaItem<*>>) -> T
): ReadOnlyProperty<VisualObject, T> {
return object : ReadOnlyProperty<VisualObject, T> {
override fun getValue(thisRef: VisualObject, property: KProperty<*>): T {
val name = key?.asName() ?: property.name.asName()
return object : ReadOnlyProperty<Any?, T> {
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
val name = key?.toName() ?: property.name.asName()
val sequence = sequence<MetaItem<*>> {
var thisObj: VisualObject? = thisRef
var thisObj: VisualObject? = this@merge
while (thisObj != null) {
thisObj.config[name]?.let { yield(it) }
thisObj = thisObj.parent

View File

@ -1,4 +1,4 @@
package hep.dataforge.vis.hmr
package hep.dataforge.vis
import kotlin.browser.document
import kotlin.dom.hasClass

View File

@ -0,0 +1,21 @@
package hep.dataforge.vis
inline fun <T : Any> jsObject(builder: T.() -> Unit): T {
val obj: T = js("({})") as T
return obj.apply {
builder()
}
}
inline fun js(builder: dynamic.() -> Unit): dynamic = jsObject(builder)
//fun <T : Any> clone(obj: T) = objectAssign(jsObject<T> {}, obj)
//inline fun <T : Any> assign(obj: T, builder: T.() -> Unit) = clone(obj).apply(builder)
fun toPlainObjectStripNull(obj: Any) = js {
for (key in Object.keys(obj)) {
val value = obj.asDynamic()[key]
if (value != null) this[key] = value
}
}

View File

@ -14,7 +14,6 @@ import hep.dataforge.vis.spatial.VisualGroup3D
import hep.dataforge.vis.spatial.VisualObject3D
import hep.dataforge.vis.spatial.rotationOrder
import scientifik.gdml.*
import kotlin.collections.set
import kotlin.random.Random
class GDMLTransformer(val root: GDML) {
@ -31,7 +30,7 @@ class GDMLTransformer(val root: GDML) {
* A special group for local templates
*/
val templates by lazy { VisualGroup3D() }
private val styles = HashMap<Name, Meta>()
private val styleCache = HashMap<Name, Meta>()
var lUnit: LUnit = LUnit.MM
@ -42,7 +41,7 @@ class GDMLTransformer(val root: GDML) {
var solidConfiguration: VisualObject3D.(parent: GDMLVolume, solid: GDMLSolid) -> Unit = { _, _ -> }
fun VisualObject.useStyle(name: String, builder: MetaBuilder.() -> Unit) {
styles.getOrPut(name.toName()){
styleCache.getOrPut(name.toName()){
buildMeta(builder)
}
applyStyle(name)
@ -60,35 +59,17 @@ class GDMLTransformer(val root: GDML) {
obj.solidConfiguration(parent, solid)
}
fun printStatistics() {
println("Solids:")
solidCounter.entries.sortedByDescending { it.value }.forEach {
println("\t$it")
}
println("Solids total: ${solidCounter.values.sum()}")
}
private val solidCounter = HashMap<String, Int>()
internal fun solidAdded(solid: GDMLSolid) {
solidCounter[solid.name] = (solidCounter[solid.name] ?: 0) + 1
}
//
// internal fun solidAdded(solid: GDMLSolid) {
// solidCounter[solid.name] = (solidCounter[solid.name] ?: 0) + 1
// }
var onFinish: GDMLTransformer.() -> Unit = {}
var optimizeSingleChild = false
//var optimizations: List<GDMLOptimization> = emptyList()
internal fun finalize(final: VisualGroup3D): VisualGroup3D {
// var res = final
// optimizations.forEach {
// res = it(res)
// }
final.templates = templates
styles.forEach {
final.setStyle(it.key, it.value)
styleCache.forEach {
final.addStyle(it.key, it.value, false)
}
final.rotationOrder = RotationOrder.ZXY
onFinish(this@GDMLTransformer)

View File

@ -1,58 +0,0 @@
package hep.dataforge.vis.spatial.gdml
import hep.dataforge.meta.update
import hep.dataforge.names.asName
import hep.dataforge.vis.common.MutableVisualGroup
import hep.dataforge.vis.spatial.Point3D
import hep.dataforge.vis.spatial.VisualGroup3D
import hep.dataforge.vis.spatial.VisualObject3D
import hep.dataforge.vis.spatial.plus
import kotlin.collections.component1
import kotlin.collections.component2
typealias GDMLOptimization = GDMLTransformer.(VisualGroup3D) -> VisualGroup3D
/**
* Collapse nodes with single child
*/
val optimizeSingleChild: GDMLOptimization = { tree ->
fun MutableVisualGroup.replaceChildren() {
children.forEach { (key, child) ->
if (child is VisualGroup3D && child.children.size == 1) {
val newChild = child.children.values.first().apply {
config.update(child.config)
}
if (newChild is VisualObject3D) {
newChild.apply {
position += child.position
rotation += child.rotation
scale = when {
scale == null && child.scale == null -> null
scale == null -> child.scale
child.scale == null -> scale
else -> Point3D(
scale!!.x * child.scale!!.x,
scale!!.y * child.scale!!.y,
scale!!.z * child.scale!!.z
)
}
}
}
if (newChild is MutableVisualGroup) {
newChild.replaceChildren()
}
//actual replacement
set(key.asName(), newChild)
} else if (child is MutableVisualGroup) {
child.replaceChildren()
}
}
}
tree.replaceChildren()
tree
}

View File

@ -47,7 +47,7 @@ private fun VisualGroup3D.addSolid(
name: String = "",
block: VisualObject3D.() -> Unit = {}
): VisualObject3D {
context.solidAdded(solid)
//context.solidAdded(solid)
val lScale = solid.lscale(context.lUnit)
val aScale = solid.ascale()
return when (solid) {
@ -150,31 +150,13 @@ private fun VisualGroup3D.addPhysicalVolume(
when (context.volumeAction(volume)) {
GDMLTransformer.Action.ACCEPT -> {
val group = volume(context, volume)
//optimizing single child case
if (context.optimizeSingleChild && group.children.size == 1) {
this[physVolume.name ?: ""] = group.children.values.first().apply {
//Must set this to avoid parent reset error
parent = null
//setting offset from physical volume
withPosition(
context.lUnit,
physVolume.resolvePosition(context.root),
physVolume.resolveRotation(context.root),
physVolume.resolveScale(context.root)
)
// in case when both phys volume and underlying volume have offset
position += group.position
rotation += group.rotation
}
} else {
this[physVolume.name ?: ""] = group.apply {
withPosition(
context.lUnit,
physVolume.resolvePosition(context.root),
physVolume.resolveRotation(context.root),
physVolume.resolveScale(context.root)
)
}
this[physVolume.name ?: ""] = group.apply {
withPosition(
context.lUnit,
physVolume.resolvePosition(context.root),
physVolume.resolveRotation(context.root),
physVolume.resolveScale(context.root)
)
}
}
GDMLTransformer.Action.CACHE -> {

View File

@ -1,33 +1,26 @@
package hep.dataforge.vis.spatial.gdml.demo
import hep.dataforge.context.Global
import hep.dataforge.vis.common.VisualGroup
import hep.dataforge.vis.hmr.ApplicationBase
import hep.dataforge.vis.hmr.startApplication
import hep.dataforge.vis.ApplicationBase
import hep.dataforge.vis.spatial.Material3D.Companion.OPACITY_KEY
import hep.dataforge.vis.spatial.Visual3DPlugin
import hep.dataforge.vis.spatial.VisualGroup3D
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.threeOutputConfig
import hep.dataforge.vis.spatial.editor.visualObjectTree
import hep.dataforge.vis.spatial.gdml.GDMLTransformer
import hep.dataforge.vis.spatial.gdml.LUnit
import hep.dataforge.vis.spatial.gdml.toVisual
import hep.dataforge.vis.spatial.three.ThreeOutput
import hep.dataforge.vis.spatial.three.ThreePlugin
import hep.dataforge.vis.spatial.three.output
import hep.dataforge.vis.spatial.tree.propertyEditor
import hep.dataforge.vis.spatial.tree.render
import hep.dataforge.vis.spatial.tree.toTree
import kotlinx.html.InputType
import hep.dataforge.vis.startApplication
import kotlinx.html.dom.append
import kotlinx.html.js.input
import kotlinx.html.js.li
import kotlinx.html.js.p
import kotlinx.html.js.ul
import org.w3c.dom.Element
import org.w3c.dom.DragEvent
import org.w3c.dom.HTMLDivElement
import org.w3c.dom.HTMLElement
import org.w3c.dom.events.Event
import org.w3c.files.FileList
import org.w3c.files.FileReader
import org.w3c.files.get
@ -40,33 +33,27 @@ private class GDMLDemoApp : ApplicationBase() {
/**
* Handle mouse drag according to https://www.html5rocks.com/en/tutorials/file/dndfiles/
*/
private fun handleDragOver(event: Event) {
private fun handleDragOver(event: DragEvent) {
event.stopPropagation()
event.preventDefault()
event.asDynamic().dataTransfer.dropEffect = "copy"
event.dataTransfer?.dropEffect = "copy"
}
/**
* Load data from text file
*/
private fun loadData(event: Event, block: (name: String, data: String) -> Unit) {
private fun loadData(event: DragEvent, block: (name: String, data: String) -> Unit) {
event.stopPropagation()
event.preventDefault()
val file = (event.asDynamic().dataTransfer.files as FileList)[0]
val file = (event.dataTransfer?.files as FileList)[0]
?: throw RuntimeException("Failed to load file")
FileReader().apply {
onload = {
val string = result as String
// try {
block(file.name, string)
// } catch (ex: Exception) {
// console.error(ex)
// }
block(file.name, string)
}
readAsText(file)
}
@ -99,53 +86,24 @@ private class GDMLDemoApp : ApplicationBase() {
}
}
fun setupLayers(element: Element, output: ThreeOutput) {
element.clear()
element.append {
ul("list-group") {
(0..9).forEach { layer ->
li("list-group-item") {
+"layer $layer"
input(type = InputType.checkBox).apply {
if (layer == 0) {
checked = true
}
onchange = {
if (checked) {
output.camera.layers.enable(layer)
} else {
output.camera.layers.disable(layer)
}
}
}
}
}
}
}
}
private val gdmlConfiguration: GDMLTransformer.() -> Unit = {
lUnit = LUnit.CM
volumeAction = { volume ->
when {
volume.name.startsWith("ecal01lay") -> GDMLTransformer.Action.REJECT
volume.name.startsWith("ecal") -> GDMLTransformer.Action.CACHE
volume.name.startsWith("UPBL") -> GDMLTransformer.Action.REJECT
volume.name.startsWith("USCL") -> GDMLTransformer.Action.REJECT
volume.name.startsWith("U") -> GDMLTransformer.Action.CACHE
volume.name.startsWith("VPBL") -> GDMLTransformer.Action.REJECT
volume.name.startsWith("VSCL") -> GDMLTransformer.Action.REJECT
volume.name.startsWith("V") -> GDMLTransformer.Action.CACHE
else -> GDMLTransformer.Action.ACCEPT
else -> GDMLTransformer.Action.CACHE
}
}
solidConfiguration = { parent, solid ->
if (parent.physVolumes.isNotEmpty()
|| solid.name.startsWith("Coil")
|| solid.name.startsWith("Yoke")
|| solid.name.startsWith("Magnet")
if (
solid.name.startsWith("Yoke")
|| solid.name.startsWith("Pole")
|| parent.physVolumes.isNotEmpty()
) {
useStyle("opaque") {
OPACITY_KEY to 0.3
@ -185,18 +143,17 @@ private class GDMLDemoApp : ApplicationBase() {
}
}
//Optimize tree
//(visual as? VisualGroup3D)?.transformInPlace(UnRef, RemoveSingleChild)
message("Rendering")
val output = three.output(canvas as HTMLElement)
//output.camera.layers.enable(1)
output.camera.layers.set(0)
setupLayers(layers, output)
val output = three.output(canvas as HTMLElement)
if (visual is VisualGroup) {
visual.toTree(editor::propertyEditor).render(tree as HTMLElement) {
showCheckboxes = false
}
}
output.camera.layers.set(0)
layers.threeOutputConfig(output)
tree.visualObjectTree(visual, editor::propertyEditor)
output.render(visual)
message(null)
@ -204,8 +161,8 @@ private class GDMLDemoApp : ApplicationBase() {
}
(document.getElementById("drop_zone") as? HTMLDivElement)?.apply {
addEventListener("dragover", { handleDragOver(it) }, false)
addEventListener("drop", { loadData(it, action) }, false)
addEventListener("dragover", { handleDragOver(it as DragEvent) }, false)
addEventListener("drop", { loadData(it as DragEvent, action) }, false)
}
}

View File

@ -29,45 +29,12 @@
<div class="container-fluid">
<div class="row">
<div class="col-9" id="canvas"></div>
<div class="col-3">
<div id="editor"></div>
<div class="accordion" id="accordion">
<div class="card">
<div class="card-header" id="layers-header">
<h2 class="mb-0">
<button class="btn btn-link" type="button" data-toggle="collapse" data-target="#layers-body"
aria-expanded="true" aria-controls="layers-body">
Layers
</button>
</h2>
</div>
<div id="layers-body" class="collapse show" aria-labelledby="layers-header"
data-parent="#accordion">
<div class="card-body">
<div id="layers"></div>
</div>
</div>
</div>
<div class="card">
<div class="card-header" id="tree-header">
<h2 class="mb-0">
<button class="btn btn-link collapsed" type="button" data-toggle="collapse"
data-target="#tree-body" aria-expanded="false" aria-controls="tree-body">
Object tree
</button>
</h2>
</div>
<div id="tree-body" class="collapse" aria-labelledby="tree-header" data-parent="#accordion">
<div class="card-body">
<div id="tree"></div>
</div>
</div>
</div>
</div>
<div class="col-lg-3" id="tree"></div>
<div class="col-lg-6">
<div class="row" id="layers"></div>
<div class="row container" id="canvas"></div>
</div>
<div class="col-lg-3" id="editor"></div>
</div>
</div>

View File

@ -0,0 +1,40 @@
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,21 +1,30 @@
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 file = File("D:\\Work\\Projects\\gdml.kt\\gdml-source\\cubes.gdml")
val xmlReader = StAXReader(file.inputStream(), "UTF-8")
val xml = GDML.format.parse(GDML.serializer(), xmlReader)
val visual = xml.toVisual {
lUnit = LUnit.CM
volumeAction = { volume ->
when {
volume.name.startsWith("ecal01lay") -> GDMLTransformer.Action.REJECT
else -> GDMLTransformer.Action.CACHE
}
}
solidConfiguration = { parent, solid ->
if (parent.physVolumes.isNotEmpty()
|| solid.name.startsWith("Coil")
@ -24,16 +33,14 @@ fun main() {
|| solid.name.startsWith("Pole")
) {
useStyle("opaque") {
opacity = 0.3
Material3D.OPACITY_KEY to 0.3
}
}
}
// optimizeSingleChild = true
//optimizations = listOf(optimizeSingleChild)
onFinish = { printStatistics() }
}
// (visual as? VisualGroup3D)?.let { UnRef(it) }?.let { RemoveSingleChild(it) }
val string = Visual3DPlugin.json.stringify(VisualGroup3D.serializer(), visual)
val tmpFile = File.createTempFile("dataforge-visual", ".json")

View File

@ -27,14 +27,9 @@ kotlin {
}
jsMain {
dependencies {
api(project(":wrappers"))
implementation(npm("three", "0.106.2"))
implementation(npm("@hi-level/three-csg", "1.0.6"))
implementation(npm("style-loader"))
implementation(npm("inspire-tree","6.0.1"))
implementation(npm("inspire-tree-dom","4.0.6"))
implementation(npm("jsoneditor"))
// api("org.jetbrains:kotlin-extensions:1.0.1-pre.83-kotlin-1.3.50")
// api(npm("core-js"))
}
}
}

View File

@ -0,0 +1,26 @@
@file:UseSerializers(Point3DSerializer::class)
package hep.dataforge.vis.spatial
import hep.dataforge.io.ConfigSerializer
import hep.dataforge.meta.Config
import hep.dataforge.vis.common.AbstractVisualObject
import hep.dataforge.vis.common.number
import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers
@Serializable
class PolyLine(var points: List<Point3D>) : AbstractVisualObject(), VisualObject3D {
@Serializable(ConfigSerializer::class)
override var properties: Config? = null
override var position: Point3D? = null
override var rotation: Point3D? = null
override var scale: Point3D? = null
//var lineType by string()
var thickness by number(1.0, key = "material.thickness")
}
fun VisualGroup3D.polyline(vararg points: Point3D, name: String = "", action: PolyLine.() -> Unit = {}) =
PolyLine(points.toList()).apply(action).also { set(name, it) }

View File

@ -35,21 +35,27 @@ class Proxy(val templateName: Name) : AbstractVisualObject(), VisualGroup, Visua
/**
* Recursively search for defined template in the parent
*/
val prototype: VisualObject3D get() = (parent as? VisualGroup3D)?.getTemplate(templateName)
?: error("Template with name $templateName not found in $parent")
val prototype: VisualObject3D
get() = (parent as? VisualGroup3D)?.getTemplate(templateName)
?: error("Template with name $templateName not found in $parent")
override fun getStyle(name: Name): Meta? = (parent as VisualGroup?)?.getStyle(name)
override fun setStyle(name: Name, meta: Meta) {
(parent as VisualGroup?)?.setStyle(name, meta)
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<*>? {
return if (inherit) {
super.getProperty(name, false) ?: prototype.getProperty(name, false) ?: parent?.getProperty(name, inherit)
properties?.get(name)
?: mergedStyles[name]
?: prototype.getProperty(name, false)
?: parent?.getProperty(name, inherit)
} else {
super.getProperty(name, false) ?: prototype.getProperty(name, false)
properties?.get(name)
?: mergedStyles[name]
?: prototype.getProperty(name, false)
}
}
@ -74,6 +80,15 @@ class Proxy(val templateName: Name) : AbstractVisualObject(), VisualGroup, Visua
?: error("Prototype with name $name not found in ${this@Proxy}")
override var styles: List<Name>
get() = super.styles + prototype.styles
set(value) {
setProperty(VisualObject.STYLE_KEY, value.map { it.toString() })
styleChanged()
}
//override fun findAllStyles(): Laminate = Laminate((styles + prototype.styles).mapNotNull { findStyle(it) })
inner class ProxyChild(val name: Name) : AbstractVisualObject(), VisualGroup {
val prototype: VisualObject by lazy {
@ -89,8 +104,8 @@ class Proxy(val templateName: Name) : AbstractVisualObject(), VisualGroup, Visua
override fun getStyle(name: Name): Meta? = this@Proxy.getStyle(name)
override fun setStyle(name: Name, meta: Meta) {
this@Proxy.setStyle(name, meta)
override fun addStyle(name: Name, meta: Meta, apply: Boolean) {
this@Proxy.addStyle(name, meta, apply)
}
override var properties: Config?
@ -113,12 +128,12 @@ class Proxy(val templateName: Name) : AbstractVisualObject(), VisualGroup, Visua
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? {
return if (inherit) {
properties?.get(name)
?: appliedStyles[name]
?: parent?.getProperty(name, inherit)
?: mergedStyles[name]
?: prototype.getProperty(name, inherit)
?: parent?.getProperty(name, inherit)
} else {
properties?.get(name)
?: appliedStyles[name]
?: mergedStyles[name]
?: prototype.getProperty(name, inherit)
}
}
@ -141,4 +156,4 @@ inline fun VisualGroup3D.ref(
templateName: Name,
name: String = "",
action: Proxy.() -> Unit = {}
) = Proxy(templateName).apply(action).also { set(name, it) }
) = Proxy(templateName).apply(action).also { set(name, it) }

View File

@ -43,7 +43,7 @@ class VisualGroup3D : AbstractVisualGroup(), VisualObject3D {
//FIXME to be lifted to AbstractVisualGroup after https://github.com/Kotlin/kotlinx.serialization/issues/378 is fixed
override var properties: Config? = null
override val styles = HashMap<Name, Meta>()
override val styleSheet = HashMap<Name, Meta>()
override var position: Point3D? = null
override var rotation: Point3D? = null

View File

@ -10,6 +10,7 @@ import hep.dataforge.output.Output
import hep.dataforge.vis.common.VisualObject
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.SELECTED_KEY
import hep.dataforge.vis.spatial.VisualObject3D.Companion.VISIBLE_KEY
import kotlinx.serialization.UseSerializers
@ -40,6 +41,7 @@ interface VisualObject3D : VisualObject {
val SELECTED_KEY = "selected".asName()
val DETAIL_KEY = "detail".asName()
val LAYER_KEY = "layer".asName()
val IGNORE_KEY = "ignore".asName()
val GEOMETRY_KEY = "geometey".asName()
@ -111,6 +113,14 @@ var VisualObject.visible: Boolean?
get() = getProperty(VISIBLE_KEY).boolean
set(value) = setProperty(VISIBLE_KEY, value)
/**
* If this property is true, the object will be ignored on render.
* Property is not inherited.
*/
var VisualObject.ignore: Boolean?
get() = getProperty(IGNORE_KEY,false).boolean
set(value) = setProperty(IGNORE_KEY, value)
var VisualObject.selected: Boolean?
get() = getProperty(SELECTED_KEY).boolean
set(value) = setProperty(SELECTED_KEY, value)

View File

@ -0,0 +1,58 @@
package hep.dataforge.vis.spatial.transform
import hep.dataforge.meta.update
import hep.dataforge.names.asName
import hep.dataforge.vis.common.MutableVisualGroup
import hep.dataforge.vis.common.VisualGroup
import hep.dataforge.vis.common.VisualObject
import hep.dataforge.vis.spatial.*
internal fun mergeChild(parent: VisualGroup, child: VisualObject): VisualObject {
return child.apply {
parent.properties?.let { config.update(it) }
if (this is VisualObject3D && parent is VisualObject3D) {
position += parent.position
rotation += parent.rotation
scale = when {
scale == null && parent.scale == null -> null
scale == null -> parent.scale
parent.scale == null -> scale
else -> Point3D(
scale!!.x * parent.scale!!.x,
scale!!.y * parent.scale!!.y,
scale!!.z * parent.scale!!.z
)
}
}
}
}
object RemoveSingleChild : VisualTreeTransform<VisualGroup3D>() {
override fun VisualGroup3D.transformInPlace() {
fun MutableVisualGroup.replaceChildren() {
children.forEach { (childName, parent) ->
if (parent is Proxy) return@forEach //ignore refs
if (parent is MutableVisualGroup) {
parent.replaceChildren()
}
if (parent is VisualGroup && parent.children.size == 1) {
val child = parent.children.values.first()
val newParent = mergeChild(parent, child)
newParent.parent = null
set(childName.asName(), newParent)
}
}
}
replaceChildren()
templates?.replaceChildren()
}
override fun VisualGroup3D.clone(): VisualGroup3D {
TODO()
}
}

View File

@ -0,0 +1,49 @@
package hep.dataforge.vis.spatial.transform
import hep.dataforge.names.Name
import hep.dataforge.names.asName
import hep.dataforge.vis.common.MutableVisualGroup
import hep.dataforge.vis.common.VisualGroup
import hep.dataforge.vis.spatial.Proxy
import hep.dataforge.vis.spatial.VisualGroup3D
object UnRef : VisualTreeTransform<VisualGroup3D>() {
private fun VisualGroup.countRefs(): Map<Name, Int> {
return children.values.fold(HashMap()) { reducer, obj ->
if (obj is VisualGroup) {
val counter = obj.countRefs()
counter.forEach { (key, value) ->
reducer[key] = (reducer[key] ?: 0) + value
}
} else if (obj is Proxy) {
reducer[obj.templateName] = (reducer[obj.templateName] ?: 0) + 1
}
return reducer
}
}
private fun MutableVisualGroup.unref(name: Name) {
(this as? VisualGroup3D)?.templates?.set(name, null)
children.filter { (it.value as? Proxy)?.templateName == name }.forEach { (key, value) ->
val proxy = value as Proxy
val newChild = mergeChild(proxy, proxy.prototype)
newChild.parent = null
set(key.asName(), newChild) // replace proxy with merged object
}
children.values.filterIsInstance<MutableVisualGroup>().forEach { it.unref(name) }
}
override fun VisualGroup3D.transformInPlace() {
val counts = countRefs()
counts.filter { it.value <= 1 }.forEach {
this.unref(it.key)
}
}
override fun VisualGroup3D.clone(): VisualGroup3D {
TODO()
}
}

View File

@ -0,0 +1,34 @@
package hep.dataforge.vis.spatial.transform
import hep.dataforge.vis.common.VisualObject
/**
* A root class for [VisualObject] tree optimization
*/
abstract class VisualTreeTransform<T : VisualObject> {
protected abstract fun T.transformInPlace()
protected abstract fun T.clone(): T
operator fun invoke(source: T, inPlace: Boolean = true): T {
val newSource = if (inPlace) {
source
} else {
source.clone()
}
newSource.transformInPlace()
return newSource
}
}
fun <T : VisualObject> T.transform(vararg transform: VisualTreeTransform<T>): T {
var res = this
transform.forEach {
res = it(res)
}
return res
}
fun <T : VisualObject> T.transformInPlace(vararg transform: VisualTreeTransform<in T>) {
transform.forEach { it(this) }
}

View File

@ -1,6 +1,6 @@
package hep.dataforge.vis.spatial
import hep.dataforge.context.Global
import hep.dataforge.vis.spatial.Visual3DPlugin.Companion.json
import kotlinx.serialization.ImplicitReflectionSerializer
import kotlin.test.Test
import kotlin.test.assertEquals
@ -12,9 +12,9 @@ class SerializationTest {
val cube = Box(100f,100f,100f).apply{
color(222)
}
val meta = cube.toMeta()
println(meta)
val newCube = Box(Global,null, meta)
val string = json.stringify(Box.serializer(),cube)
println(string)
val newCube = json.parse(Box.serializer(),string)
assertEquals(cube.toMeta(),newCube.toMeta())
}
}

View File

@ -0,0 +1,15 @@
package hep.dataforge.vis.spatial.editor
import kotlinx.html.TagConsumer
import kotlinx.html.js.div
import kotlinx.html.js.h3
import org.w3c.dom.HTMLElement
inline fun TagConsumer<HTMLElement>.card(title: String, crossinline block: TagConsumer<HTMLElement>.() -> Unit) {
div("card w-100") {
div("card-body") {
h3(classes = "card-title") { +title }
block()
}
}
}

View File

@ -1,6 +1,6 @@
@file:Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE")
package hep.dataforge.vis.spatial.tree
package hep.dataforge.vis.spatial.editor
import hep.dataforge.meta.string
import hep.dataforge.names.EmptyName
@ -9,21 +9,19 @@ 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 org.w3c.dom.HTMLElement
import info.laht.threekt.loaders.Cache.clear
import kotlinx.html.div
import kotlinx.html.dom.append
import org.w3c.dom.Element
import kotlin.js.json
operator fun Name.plus(other: NameToken): Name = Name(tokens + other)
fun InspireTree.render(element: HTMLElement, block: DomConfig.() -> Unit = {}) {
val config = (json(
"target" to element
) as DomConfig).apply(block)
InspireTreeDOM(this, config)
}
internal fun createInspireTree(block: Config.() -> Unit = {}): InspireTree {
private fun createInspireTree(block: Config.() -> Unit = {}): InspireTree {
val config = (json(
"checkbox" to json(
"autoCheckChildren" to false
@ -32,7 +30,7 @@ internal fun createInspireTree(block: Config.() -> Unit = {}): InspireTree {
return InspireTree(config)
}
fun VisualGroup.toTree(onFocus: (VisualObject?, String?) -> Unit = { _, _ -> }): InspireTree {
private fun VisualObject.toTree(onFocus: (VisualObject?, String?) -> Unit = { _, _ -> }): InspireTree {
val map = HashMap<String, VisualObject>()
@ -67,14 +65,18 @@ fun VisualGroup.toTree(onFocus: (VisualObject?, String?) -> Unit = { _, _ -> }):
}
fun TreeNode.fillChildren(group: VisualGroup, groupName: Name) {
group.children.forEach { (token, obj) ->
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)
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)
}
}
}
}
}
@ -119,3 +121,16 @@ fun VisualGroup.toTree(onFocus: (VisualObject?, String?) -> Unit = { _, _ -> }):
return inspireTree
}
fun Element.visualObjectTree(group: VisualObject, onFocus: (VisualObject?, String?) -> Unit) {
clear()
append {
card("Visual object tree") {
val domConfig = jsObject<DomConfig> {
target = div()
showCheckboxes = false
}
InspireTreeDOM(group.toTree(onFocus), domConfig)
}
}
}

View File

@ -0,0 +1,37 @@
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

@ -0,0 +1,63 @@
package hep.dataforge.vis.spatial.editor
import hep.dataforge.io.toJson
import hep.dataforge.meta.*
import hep.dataforge.vis.common.VisualObject
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.js.div
import kotlinx.html.js.h4
import org.w3c.dom.Element
import kotlin.dom.clear
//FIXME something rotten in JS-Meta converter
fun Meta.toDynamic() = JSON.parse<dynamic>(toJson().toString())
fun Element.propertyEditor(item: VisualObject?, name: String?) {
clear()
if (item != null) {
append {
card("Properties") {
val config = (item.properties ?: item.prototype?.properties) ?: EmptyMeta
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 {
mode = "form"
onChangeJSON = { item.config.update(DynamicMeta(it.asDynamic())) }
}
JSONEditor(div(), options, dMeta)
}
val styles = item.styles
if (styles.isNotEmpty()) {
card("Styles") {
item.styles.forEach { style ->
val styleMeta = item.findStyle(style)
h4("container") { +style.toString() }
if (styleMeta != null) {
div("container").apply {
val options: JSONEditorOptions = jsObject {
mode = "view"
}
JSONEditor(this, options, styleMeta.toDynamic())
}
}
}
}
}
}
}
}

View File

@ -4,6 +4,7 @@ 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
@ -13,8 +14,37 @@ import info.laht.threekt.math.Color
object Materials {
val DEFAULT_COLOR = Color(Colors.darkgreen)
val DEFAULT = MeshPhongMaterial().apply {
this.color.set(DEFAULT_COLOR)
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
}
}
}
/**
@ -41,26 +71,26 @@ fun MetaItem<*>.color(): Color {
}
}
private val materialCache = HashMap<Meta, Material>()
/**
* Infer Three material based on meta item
*/
fun Meta?.jsMaterial(): Material {
return if (this == null) {
Materials.DEFAULT
} else
//TODO add more options for material
return materialCache.getOrPut(this) {
MeshBasicMaterial().apply {
color = get("color")?.color() ?: Materials.DEFAULT_COLOR
opacity = get("opacity")?.double ?: 1.0
transparent = get("transparent").boolean ?: (opacity < 1.0)
//node["specularColor"]?.let { specular = it.color() }
//side = 2
}
}
} else {
Materials.getMaterial(this)
}
}
fun Material3D?.jsMaterial(): Material = this?.config.jsMaterial()
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

@ -0,0 +1,99 @@
package hep.dataforge.vis.spatial.three
import hep.dataforge.meta.boolean
import hep.dataforge.meta.node
import hep.dataforge.names.asName
import hep.dataforge.names.plus
import hep.dataforge.names.startsWith
import hep.dataforge.vis.spatial.Material3D
import hep.dataforge.vis.spatial.VisualObject3D
import hep.dataforge.vis.spatial.layer
import hep.dataforge.vis.spatial.material
import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.geometries.EdgesGeometry
import info.laht.threekt.geometries.WireframeGeometry
import info.laht.threekt.objects.LineSegments
import info.laht.threekt.objects.Mesh
import kotlin.reflect.KClass
/**
* Basic geometry-based factory
*/
abstract class MeshThreeFactory<T : VisualObject3D>(
override val type: KClass<out T>
) : ThreeFactory<T> {
/**
* Build a geometry for an object
*/
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 {
//TODO add caching for geometries using templates
val geometry = buildGeometry(obj)
//JS sometimes tries to pass Geometry as BufferGeometry
@Suppress("USELESS_IS_CHECK") if (geometry !is BufferGeometry) error("BufferGeometry expected")
val mesh = Mesh(geometry, obj.material.jsMaterial()).apply {
matrixAutoUpdate = false
applyEdges(obj)
applyWireFrame(obj)
//set position for mesh
updatePosition(obj)
layers.enable(obj.layer)
children.forEach {
it.layers.enable(obj.layer)
}
}
//add listener to object properties
obj.onPropertyChange(this) { name, _, _ ->
when {
name.startsWith(VisualObject3D.GEOMETRY_KEY) -> mesh.geometry = buildGeometry(obj)
name.startsWith(WIREFRAME_KEY) -> mesh.applyWireFrame(obj)
name.startsWith(EDGES_KEY) -> mesh.applyEdges(obj)
else -> mesh.updateProperty(obj, name)
}
}
return mesh
}
companion object {
val EDGES_KEY = "edges".asName()
val WIREFRAME_KEY = "wireframe".asName()
val ENABLED_KEY = "enabled".asName()
val EDGES_ENABLED_KEY = EDGES_KEY + ENABLED_KEY
val EDGES_MATERIAL_KEY = EDGES_KEY + Material3D.MATERIAL_KEY
val WIREFRAME_ENABLED_KEY = WIREFRAME_KEY + ENABLED_KEY
val WIREFRAME_MATERIAL_KEY = WIREFRAME_KEY + Material3D.MATERIAL_KEY
}
}

View File

@ -1,22 +1,14 @@
package hep.dataforge.vis.spatial.three
import hep.dataforge.meta.boolean
import hep.dataforge.meta.node
import hep.dataforge.names.Name
import hep.dataforge.names.asName
import hep.dataforge.names.plus
import hep.dataforge.names.startsWith
import hep.dataforge.provider.Type
import hep.dataforge.vis.common.VisualObject
import hep.dataforge.vis.spatial.*
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_KEY
import hep.dataforge.vis.spatial.VisualObject3D.Companion.GEOMETRY_KEY
import hep.dataforge.vis.spatial.three.ThreeFactory.Companion.TYPE
import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.Object3D
import info.laht.threekt.geometries.EdgesGeometry
import info.laht.threekt.geometries.WireframeGeometry
import info.laht.threekt.objects.LineSegments
import info.laht.threekt.objects.Mesh
import kotlin.reflect.KClass
@ -46,30 +38,6 @@ internal fun Object3D.updatePosition(obj: VisualObject3D) {
updateMatrix()
}
internal fun <T : VisualObject3D> Mesh.updateFrom(obj: T) {
matrixAutoUpdate = false
//inherited edges definition, enabled by default
if (obj.getProperty(MeshThreeFactory.EDGES_ENABLED_KEY).boolean != false) {
val material = obj.getProperty(MeshThreeFactory.EDGES_MATERIAL_KEY).node?.jsMaterial() ?: Materials.DEFAULT
add(LineSegments(EdgesGeometry(geometry as BufferGeometry), material))
}
//inherited wireframe definition, disabled by default
if (obj.getProperty(MeshThreeFactory.WIREFRAME_ENABLED_KEY).boolean == true) {
val material = obj.getProperty(MeshThreeFactory.WIREFRAME_MATERIAL_KEY).node?.jsMaterial() ?: Materials.DEFAULT
add(LineSegments(WireframeGeometry(geometry as BufferGeometry), material))
}
//set position for mesh
updatePosition(obj)
layers.enable(obj.layer)
children.forEach {
it.layers.enable(obj.layer)
}
}
/**
* Unsafe invocation of a factory
*/
@ -82,53 +50,6 @@ operator fun <T : VisualObject3D> ThreeFactory<T>.invoke(obj: Any): Object3D {
}
}
/**
* Basic geometry-based factory
*/
abstract class MeshThreeFactory<T : VisualObject3D>(override val type: KClass<out T>) : ThreeFactory<T> {
/**
* Build a geometry for an object
*/
abstract fun buildGeometry(obj: T): BufferGeometry
override fun invoke(obj: T): Mesh {
//create mesh from geometry
return buildMesh(obj) { buildGeometry(it) }
}
companion object {
val EDGES_KEY = "edges".asName()
val WIREFRAME_KEY = "wireframe".asName()
val ENABLED_KEY = "enabled".asName()
val EDGES_ENABLED_KEY = EDGES_KEY + ENABLED_KEY
val EDGES_MATERIAL_KEY = EDGES_KEY + MATERIAL_KEY
val WIREFRAME_ENABLED_KEY = WIREFRAME_KEY + ENABLED_KEY
val WIREFRAME_MATERIAL_KEY = WIREFRAME_KEY + MATERIAL_KEY
fun <T : VisualObject3D> buildMesh(obj: T, geometryBuilder: (T) -> BufferGeometry): Mesh {
//TODO add caching for geometries using templates
val geometry = geometryBuilder(obj)
//JS sometimes tries to pass Geometry as BufferGeometry
@Suppress("USELESS_IS_CHECK") if (geometry !is BufferGeometry) error("BufferGeometry expected")
val mesh = Mesh(geometry, obj.material.jsMaterial())
mesh.updateFrom(obj)
//add listener to object properties
obj.onPropertyChange(this) { name, _, _ ->
mesh.updateProperty(obj, name)
if (name.startsWith(GEOMETRY_KEY)) {
mesh.geometry = geometryBuilder(obj)
}
}
return mesh
}
}
}
fun Object3D.updateProperty(source: VisualObject, propertyName: Name) {
if (this is Mesh && propertyName.startsWith(MATERIAL_KEY)) {
//updated material

View File

@ -0,0 +1,32 @@
package hep.dataforge.vis.spatial.three
import hep.dataforge.vis.spatial.PolyLine
import hep.dataforge.vis.spatial.layer
import hep.dataforge.vis.spatial.material
import info.laht.threekt.core.Geometry
import info.laht.threekt.core.Object3D
import info.laht.threekt.objects.LineSegments
import kotlin.reflect.KClass
object ThreeLineFactory : ThreeFactory<PolyLine> {
override val type: KClass<out PolyLine> get() = PolyLine::class
override fun invoke(obj: PolyLine): Object3D {
val geometry = Geometry().apply {
vertices = obj.points.toTypedArray()
}
val material = obj.material.jsLineMaterial()
return LineSegments(geometry, material).apply {
updatePosition(obj)
layers.enable(obj.layer)
//add listener to object properties
obj.onPropertyChange(this) { propertyName, _, _ ->
updateProperty(obj, propertyName)
}
}
}
}

View File

@ -33,6 +33,7 @@ class ThreePlugin : AbstractPlugin() {
objectFactories[Convex::class] = ThreeConvexFactory
objectFactories[Sphere::class] = ThreeSphereFactory
objectFactories[ConeSegment::class] = ThreeCylinderFactory
objectFactories[PolyLine::class] = ThreeLineFactory
}
private fun findObjectFactory(type: KClass<out VisualObject3D>): ThreeFactory<*>? {
@ -46,7 +47,7 @@ class ThreePlugin : AbstractPlugin() {
is VisualGroup3D -> {
val group = ThreeGroup()
obj.children.forEach { (name, child) ->
if (child is VisualObject3D) {
if (child is VisualObject3D && child.ignore != true) {
try {
val object3D = buildObject3D(child)
object3D.name = name.toString()

View File

@ -1,98 +0,0 @@
package hep.dataforge.vis.spatial.tree
import hep.dataforge.io.toJson
import hep.dataforge.meta.DynamicMeta
import hep.dataforge.meta.builder
import hep.dataforge.meta.update
import hep.dataforge.vis.common.VisualObject
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.InputType
import kotlinx.html.dom.append
import kotlinx.html.js.*
import org.w3c.dom.Element
import kotlin.dom.clear
fun Element.propertyEditor(item: VisualObject?, name: String?) {
clear()
if (item != null) {
append {
div("card") {
div("card-body") {
h3(classes = "card-title") {
+(name ?: "")
}
form {
div("form-group row") {
label("col-form-label col-4") {
+"Color: "
}
input(InputType.color, classes = "form-control col-8") {
value = item.color ?: "#ffffff"
}.apply {
onInputFunction = {
item.color = value
}
}
}
div("form-group row") {
label("col-form-label col-4") {
+"Opacity: "
}
input(InputType.range, classes = "form-control col-8") {
min = "0.0"
max = "1.0"
step = "0.1"
value = item.opacity.toString()
}.apply {
onInputFunction = {
item.opacity = value.toDouble()
}
}
}
div("form-group row") {
label("col-form-label col-4") { +"Visible: " }
div("col-8") {
div("form-check") {
input(InputType.checkBox, classes = "form-check-input").apply {
this.checked = item.visible ?: true
onInputFunction = {
item.visible = checked
}
}
}
}
}
}
}
}
(item.properties ?: item.prototype?.properties)?.let { config ->
div("card") {
div("card-body") {
h3(classes = "card-title") { +"Properties" }
}.apply {
val metaToEdit = config.builder().apply {
VISIBLE_KEY to (item.visible ?: true)
COLOR_KEY to (item.color ?: "#ffffff")
OPACITY_KEY to (item.opacity ?: 1.0)
}
//FIXME something rotten in JS-Meta converter
val jsObject: dynamic = JSON.parse(metaToEdit.toJson().toString())
//jsObject.material.color != null
val options = (js("{}") as JSONEditorOptions).apply {
mode = "form"
onChangeJSON = { item.config.update(DynamicMeta(it.asDynamic())) }
}
JSONEditor(this, options, jsObject)
}
}
}
}
}
}

View File

@ -28,7 +28,10 @@
package info.laht.threekt.objects
import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.Geometry
import info.laht.threekt.core.Object3D
import info.laht.threekt.materials.Material
open external class LineSegments(geometry: BufferGeometry, material: Material) : Object3D
open external class LineSegments(geometry: BufferGeometry, material: Material) : Object3D {
constructor(geometry: Geometry, material: Material)
}

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.5-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

4
gradlew vendored
View File

@ -125,8 +125,8 @@ if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`

View File

@ -5,6 +5,7 @@ pluginManagement {
gradlePluginPortal()
maven("https://kotlin.bintray.com/kotlinx")
maven("https://dl.bintray.com/kotlin/kotlin-eap")
maven("https://dl.bintray.com/mipt-npm/dataforge")
maven("https://dl.bintray.com/mipt-npm/scientifik")
maven("https://dl.bintray.com/mipt-npm/dev")
}
@ -17,7 +18,6 @@ pluginManagement {
"org.jetbrains.kotlin.jvm" -> useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:${requested.version}")
"org.jetbrains.kotlin.js" -> useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:${requested.version}")
"kotlin-dce-js" -> useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:${requested.version}")
"kotlin2js" -> useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:${requested.version}")
"org.jetbrains.kotlin.frontend" -> useModule("org.jetbrains.kotlin:kotlin-frontend-plugin:${requested.version}")
"scientifik.mpp", "scientifik.publish", "scientifik.jvm", "scientifik.js" -> useModule("scientifik:gradle-tools:${requested.version}")
"org.openjfx.javafxplugin" -> useModule("org.openjfx:javafx-plugin:${requested.version}")
@ -32,6 +32,7 @@ rootProject.name = "dataforge-vis"
include(
":dataforge-vis-common",
":wrappers",
":dataforge-vis-fx",
":dataforge-vis-spatial",
":dataforge-vis-spatial-gdml",

View File

@ -1,10 +1,10 @@
package hep.dataforge.vis.spatial.demo
import hep.dataforge.context.ContextBuilder
import hep.dataforge.vis.ApplicationBase
import hep.dataforge.vis.common.Colors
import hep.dataforge.vis.hmr.ApplicationBase
import hep.dataforge.vis.hmr.startApplication
import hep.dataforge.vis.spatial.*
import hep.dataforge.vis.startApplication
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
@ -130,6 +130,21 @@ private class ThreeDemoApp : ApplicationBase() {
}
}
}
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
}
}
}
}

23
wrappers/build.gradle.kts Normal file
View File

@ -0,0 +1,23 @@
plugins {
id("scientifik.js")
//id("kotlin-dce-js")
}
dependencies {
api(project(":dataforge-vis-common"))
testCompile(kotlin("test-js"))
}
kotlin{
sourceSets["main"].apply{
dependencies{
api(npm("style-loader"))
api(npm("inspire-tree","6.0.1"))
api(npm("inspire-tree-dom","4.0.6"))
api(npm("jsoneditor"))
api(npm("dat.gui"))
//api("org.jetbrains:kotlin-extensions:1.0.1-pre.83-kotlin-1.3.50")
}
}
}

View File

@ -0,0 +1,102 @@
@file:Suppress(
"INTERFACE_WITH_SUPERCLASS",
"OVERRIDING_FINAL_MEMBER",
"RETURN_TYPE_MISMATCH_ON_OVERRIDE",
"CONFLICTING_OVERLOADS",
"EXTERNAL_DELEGATION"
)
@file:JsModule("dat.gui")
@file:JsNonModule
package hep.dataforge.vis.spatial.editor
import org.w3c.dom.HTMLElement
external interface GUIParams {
var autoPlace: Boolean? get() = definedExternally; set(value) = definedExternally
var closed: Boolean? get() = definedExternally; set(value) = definedExternally
var closeOnTop: Boolean? get() = definedExternally; set(value) = definedExternally
var hideable: Boolean? get() = definedExternally; set(value) = definedExternally
var load: Any? get() = definedExternally; set(value) = definedExternally
var name: String? get() = definedExternally; set(value) = definedExternally
var preset: String? get() = definedExternally; set(value) = definedExternally
var width: Number? get() = definedExternally; set(value) = definedExternally
}
external open class GUI(option: GUIParams? = definedExternally /* null */) {
open var __controllers: Array<GUIController>
open var __folders: Array<GUI>
open var domElement: HTMLElement
open fun add(
target: Any,
propName: String,
min: Number? = definedExternally /* null */,
max: Number? = definedExternally /* null */,
step: Number? = definedExternally /* null */
): GUIController
open fun add(target: Any, propName: String, status: Boolean): GUIController
open fun add(target: Any, propName: String, items: Array<String>): GUIController
open fun add(target: Any, propName: String, items: Array<Number>): GUIController
open fun add(target: Any, propName: String, items: Any): GUIController
open fun addColor(target: Any, propName: String): GUIController
open fun remove(controller: GUIController)
open fun destroy()
open fun addFolder(propName: String): GUI
open fun removeFolder(subFolder: GUI)
open fun open()
open fun close()
open fun hide()
open fun show()
open fun remember(target: Any, vararg additionalTargets: Any)
open fun getRoot(): GUI
open fun getSaveObject(): Any
open fun save()
open fun saveAs(presetName: String)
open fun revert(gui: GUI)
open fun listen(controller: GUIController)
open fun updateDisplay()
open var parent: GUI
open var scrollable: Boolean
open var autoPlace: Boolean
open var preset: String
open var width: Number
open var name: String
open var closed: Boolean
open var load: Any
open var useLocalStorage: Boolean
companion object {
var CLASS_AUTO_PLACE: String
var CLASS_AUTO_PLACE_CONTAINER: String
var CLASS_MAIN: String
var CLASS_CONTROLLER_ROW: String
var CLASS_TOO_TALL: String
var CLASS_CLOSED: String
var CLASS_CLOSE_BUTTON: String
var CLASS_CLOSE_TOP: String
var CLASS_CLOSE_BOTTOM: String
var CLASS_DRAG: String
var DEFAULT_WIDTH: Number
var TEXT_CLOSED: String
var TEXT_OPEN: String
}
}
external open class GUIController {
open fun destroy()
open var onChange: (value: Any? /* = null */) -> GUIController
open var onFinishChange: (value: Any? /* = null */) -> GUIController
open fun setValue(value: Any): GUIController
open fun getValue(): Any
open fun updateDisplay(): GUIController
open fun isModified(): Boolean
open fun min(n: Number): GUIController
open fun max(n: Number): GUIController
open fun step(n: Number): GUIController
open fun fire(): GUIController
open fun options(option: Any): GUIController
open fun name(s: String): GUIController
open fun listen(): GUIController
open fun remove(): GUIController
}

View File

@ -8,7 +8,7 @@
@file:JsModule("eventemitter2")
@file: JsNonModule
package hep.dataforge.vis.spatial.tree
package hep.dataforge.vis.spatial.editor
import kotlin.js.Promise

View File

@ -6,7 +6,7 @@
"EXTERNAL_DELEGATION"
)
package hep.dataforge.vis.spatial.tree
package hep.dataforge.vis.spatial.editor
import org.w3c.dom.HTMLElement

View File

@ -7,7 +7,7 @@
"unused"
)
package hep.dataforge.vis.spatial.tree
package hep.dataforge.vis.spatial.editor
import kotlin.js.Promise
import kotlin.js.RegExp

View File

@ -6,7 +6,7 @@
"EXTERNAL_DELEGATION"
)
package hep.dataforge.vis.spatial.tree
package hep.dataforge.vis.spatial.editor
import org.w3c.dom.HTMLElement