Remove dataforge-output.
This commit is contained in:
parent
613624ff17
commit
6a48948c15
@ -85,7 +85,7 @@ also referred to as templates). The idea is that prototype geometry can be rende
|
|||||||
for multiple objects. This helps to significantly decrease memory usage.
|
for multiple objects. This helps to significantly decrease memory usage.
|
||||||
|
|
||||||
The `prototypes` property tree is defined in `SolidGroup` class via `PrototypeHolder` interface, and
|
The `prototypes` property tree is defined in `SolidGroup` class via `PrototypeHolder` interface, and
|
||||||
`Proxy` class helps to reuse a template object.
|
`SolidReference` class helps to reuse a template object.
|
||||||
|
|
||||||
##### Styles
|
##### Styles
|
||||||
|
|
||||||
|
@ -6,7 +6,10 @@ plugins {
|
|||||||
|
|
||||||
val dataforgeVersion by extra("0.2.0-dev-7")
|
val dataforgeVersion by extra("0.2.0-dev-7")
|
||||||
val ktorVersion by extra("1.4.2")
|
val ktorVersion by extra("1.4.2")
|
||||||
|
val htmlVersion by extra("0.7.2")
|
||||||
val kotlinWrappersVersion by extra("pre.129-kotlin-1.4.10")
|
val kotlinWrappersVersion by extra("pre.129-kotlin-1.4.10")
|
||||||
|
val fxVersion by extra("14")
|
||||||
|
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
@ -21,7 +24,6 @@ allprojects {
|
|||||||
|
|
||||||
val githubProject by extra("visionforge")
|
val githubProject by extra("visionforge")
|
||||||
val bintrayRepo by extra("dataforge")
|
val bintrayRepo by extra("dataforge")
|
||||||
val fxVersion by extra("14")
|
|
||||||
|
|
||||||
subprojects {
|
subprojects {
|
||||||
if(name.startsWith("visionforge")) {
|
if(name.startsWith("visionforge")) {
|
||||||
|
@ -5,12 +5,11 @@ import hep.dataforge.meta.Meta
|
|||||||
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.output.OutputManager
|
|
||||||
import hep.dataforge.output.Renderer
|
import hep.dataforge.output.Renderer
|
||||||
import hep.dataforge.vision.Vision
|
import hep.dataforge.vision.Vision
|
||||||
import hep.dataforge.vision.solid.three.ThreeCanvas
|
import hep.dataforge.vision.solid.three.ThreeCanvas
|
||||||
import hep.dataforge.vision.solid.three.ThreePlugin
|
import hep.dataforge.vision.solid.three.ThreePlugin
|
||||||
import hep.dataforge.vision.solid.three.output
|
import hep.dataforge.vision.solid.three.attachRenderer
|
||||||
import kotlinx.browser.document
|
import kotlinx.browser.document
|
||||||
import kotlinx.dom.clear
|
import kotlinx.dom.clear
|
||||||
import kotlinx.html.dom.append
|
import kotlinx.html.dom.append
|
||||||
@ -23,7 +22,7 @@ import kotlinx.html.span
|
|||||||
import org.w3c.dom.Element
|
import org.w3c.dom.Element
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
class ThreeDemoGrid(element: Element, meta: Meta = Meta.EMPTY) : OutputManager {
|
class ThreeDemoGrid(element: Element, meta: Meta = Meta.EMPTY) {
|
||||||
|
|
||||||
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()
|
||||||
@ -46,7 +45,7 @@ class ThreeDemoGrid(element: Element, meta: Meta = Meta.EMPTY) : OutputManager {
|
|||||||
span("border") {
|
span("border") {
|
||||||
div("col-6") {
|
div("col-6") {
|
||||||
div { id = "output-$name" }.also {
|
div { id = "output-$name" }.also {
|
||||||
output = three.output(it, canvasOptions)
|
output = three.attachRenderer(it, canvasOptions)
|
||||||
//output.attach(it)
|
//output.attach(it)
|
||||||
}
|
}
|
||||||
hr()
|
hr()
|
||||||
|
@ -3,7 +3,6 @@ package hep.dataforge.vision.bootstrap
|
|||||||
import hep.dataforge.vision.react.flexColumn
|
import hep.dataforge.vision.react.flexColumn
|
||||||
import hep.dataforge.vision.react.flexRow
|
import hep.dataforge.vision.react.flexRow
|
||||||
import hep.dataforge.vision.solid.SolidGroup
|
import hep.dataforge.vision.solid.SolidGroup
|
||||||
import hep.dataforge.vision.solid.SolidManager
|
|
||||||
import hep.dataforge.vision.solid.three.ThreeCanvas
|
import hep.dataforge.vision.solid.three.ThreeCanvas
|
||||||
import kotlinx.css.*
|
import kotlinx.css.*
|
||||||
import kotlinx.css.properties.border
|
import kotlinx.css.properties.border
|
||||||
@ -45,7 +44,7 @@ public external interface CanvasControlsProps : RProps {
|
|||||||
|
|
||||||
public val CanvasControls: FunctionalComponent<CanvasControlsProps> = functionalComponent("CanvasControls") { props ->
|
public val CanvasControls: FunctionalComponent<CanvasControlsProps> = functionalComponent("CanvasControls") { props ->
|
||||||
val visionManager = useMemo(
|
val visionManager = useMemo(
|
||||||
{ props.canvas.context.plugins.fetch(SolidManager).visionManager },
|
{ props.canvas.three.solidManager.visionManager },
|
||||||
arrayOf(props.canvas)
|
arrayOf(props.canvas)
|
||||||
)
|
)
|
||||||
flexColumn {
|
flexColumn {
|
||||||
|
@ -7,7 +7,6 @@ import hep.dataforge.vision.solid.Solid
|
|||||||
import hep.dataforge.vision.solid.specifications.Canvas3DOptions
|
import hep.dataforge.vision.solid.specifications.Canvas3DOptions
|
||||||
import hep.dataforge.vision.solid.three.ThreeCanvas
|
import hep.dataforge.vision.solid.three.ThreeCanvas
|
||||||
import hep.dataforge.vision.solid.three.ThreePlugin
|
import hep.dataforge.vision.solid.three.ThreePlugin
|
||||||
import hep.dataforge.vision.solid.three.output
|
|
||||||
import kotlinx.css.*
|
import kotlinx.css.*
|
||||||
import org.w3c.dom.Element
|
import org.w3c.dom.Element
|
||||||
import org.w3c.dom.HTMLElement
|
import org.w3c.dom.HTMLElement
|
||||||
@ -40,7 +39,7 @@ public val ThreeCanvasComponent: FunctionalComponent<ThreeCanvasProps> = functio
|
|||||||
val element = elementRef.current as? HTMLElement ?: error("Canvas element not found")
|
val element = elementRef.current as? HTMLElement ?: error("Canvas element not found")
|
||||||
val three: ThreePlugin = props.context.plugins.fetch(ThreePlugin)
|
val three: ThreePlugin = props.context.plugins.fetch(ThreePlugin)
|
||||||
val newCanvas: ThreeCanvas =
|
val newCanvas: ThreeCanvas =
|
||||||
three.output(element, props.options ?: Canvas3DOptions.empty(), props.clickCallback)
|
three.attachRenderer(element, props.options ?: Canvas3DOptions.empty(), props.clickCallback)
|
||||||
props.canvasCallback?.invoke(newCanvas)
|
props.canvasCallback?.invoke(newCanvas)
|
||||||
canvas = newCanvas
|
canvas = newCanvas
|
||||||
}
|
}
|
||||||
|
@ -4,13 +4,15 @@ plugins {
|
|||||||
|
|
||||||
val dataforgeVersion: String by rootProject.extra
|
val dataforgeVersion: String by rootProject.extra
|
||||||
val kotlinWrappersVersion: String by rootProject.extra
|
val kotlinWrappersVersion: String by rootProject.extra
|
||||||
|
val htmlVersion: String by rootProject.extra
|
||||||
|
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
sourceSets {
|
sourceSets {
|
||||||
commonMain {
|
commonMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
api("hep.dataforge:dataforge-output:$dataforgeVersion")
|
api("hep.dataforge:dataforge-context:$dataforgeVersion")
|
||||||
|
api("org.jetbrains.kotlinx:kotlinx-html:$htmlVersion")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jvmMain {
|
jvmMain {
|
||||||
@ -27,7 +29,6 @@ kotlin {
|
|||||||
}
|
}
|
||||||
jsMain {
|
jsMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
api("hep.dataforge:dataforge-output-html:$dataforgeVersion")
|
|
||||||
api("org.jetbrains:kotlin-extensions:1.0.1-$kotlinWrappersVersion")
|
api("org.jetbrains:kotlin-extensions:1.0.1-$kotlinWrappersVersion")
|
||||||
api("org.jetbrains:kotlin-css:1.0.0-$kotlinWrappersVersion")
|
api("org.jetbrains:kotlin-css:1.0.0-$kotlinWrappersVersion")
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
package hep.dataforge.vision
|
||||||
|
|
||||||
|
public fun interface Renderer<in V: Vision> {
|
||||||
|
public fun render(vision: V)
|
||||||
|
}
|
@ -69,7 +69,7 @@ public open class VisionBase : Vision {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun removeChangeListener(owner: Any?) {
|
override fun removeChangeListener(owner: Any?) {
|
||||||
listeners.removeAll { it.owner == owner }
|
listeners.removeAll { owner == null || it.owner == owner }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -87,12 +87,12 @@ public interface MutableVisionGroup : VisionGroup, VisionContainerBuilder<Vision
|
|||||||
* @param owner the handler to properly remove listeners
|
* @param owner the handler to properly remove listeners
|
||||||
* @param action First argument of the action is the name of changed child. Second argument is the new value of the object.
|
* @param action First argument of the action is the name of changed child. Second argument is the new value of the object.
|
||||||
*/
|
*/
|
||||||
public fun onChildrenChange(owner: Any?, action: (NameToken, Vision?) -> Unit)
|
public fun onStructureChange(owner: Any?, action: (token: NameToken, before: Vision?, after: Vision?) -> Unit)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove children change listener
|
* Remove children change listener
|
||||||
*/
|
*/
|
||||||
public fun removeChildrenChangeListener(owner: Any?)
|
public fun removeStructureChangeListener(owner: Any?)
|
||||||
|
|
||||||
// public operator fun set(name: Name, child: Vision?)
|
// public operator fun set(name: Name, child: Vision?)
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,10 @@ public open class VisionGroupBase : VisionBase(), MutableVisionGroup {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private data class StructureChangeListener(val owner: Any?, val callback: (NameToken, Vision?) -> Unit)
|
private data class StructureChangeListener(
|
||||||
|
val owner: Any?,
|
||||||
|
val callback: (token: NameToken, before: Vision?, after: Vision?) -> Unit,
|
||||||
|
)
|
||||||
|
|
||||||
@Transient
|
@Transient
|
||||||
private val structureChangeListeners = HashSet<StructureChangeListener>()
|
private val structureChangeListeners = HashSet<StructureChangeListener>()
|
||||||
@ -53,7 +56,7 @@ public open class VisionGroupBase : VisionBase(), MutableVisionGroup {
|
|||||||
/**
|
/**
|
||||||
* Add listener for children change
|
* Add listener for children change
|
||||||
*/
|
*/
|
||||||
override fun onChildrenChange(owner: Any?, action: (NameToken, Vision?) -> Unit) {
|
override fun onStructureChange(owner: Any?, action: (token: NameToken, before: Vision?, after: Vision?) -> Unit) {
|
||||||
structureChangeListeners.add(
|
structureChangeListeners.add(
|
||||||
StructureChangeListener(
|
StructureChangeListener(
|
||||||
owner,
|
owner,
|
||||||
@ -65,29 +68,31 @@ public open class VisionGroupBase : VisionBase(), MutableVisionGroup {
|
|||||||
/**
|
/**
|
||||||
* Remove children change listener
|
* Remove children change listener
|
||||||
*/
|
*/
|
||||||
override fun removeChildrenChangeListener(owner: Any?) {
|
override fun removeStructureChangeListener(owner: Any?) {
|
||||||
structureChangeListeners.removeAll { it.owner === owner }
|
structureChangeListeners.removeAll { owner == null || it.owner === owner }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Propagate children change event upwards
|
* Propagate children change event upwards
|
||||||
*/
|
*/
|
||||||
protected fun childrenChanged(name: NameToken, child: Vision?) {
|
protected fun childrenChanged(name: NameToken, before: Vision?, after: Vision?) {
|
||||||
structureChangeListeners.forEach { it.callback(name, child) }
|
structureChangeListeners.forEach { it.callback(name, before, after) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove a child with given name token
|
* Remove a child with given name token
|
||||||
*/
|
*/
|
||||||
public fun removeChild(token: NameToken) {
|
public fun removeChild(token: NameToken): Vision? {
|
||||||
childrenInternal.remove(token)
|
val removed = childrenInternal.remove(token)
|
||||||
|
removed?.parent = null
|
||||||
|
return removed
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a static child. Statics could not be found by name, removed or replaced. Changing statics also do not trigger events.
|
* Add a static child. Statics could not be found by name, removed or replaced. Changing statics also do not trigger events.
|
||||||
*/
|
*/
|
||||||
protected open fun addStatic(child: Vision): Unit {
|
protected open fun addStatic(child: Vision): Unit {
|
||||||
attach(NameToken("@static", index = child.hashCode().toString()), child)
|
attachChild(NameToken("@static", index = child.hashCode().toString()), child)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun createGroup(): VisionGroupBase = VisionGroupBase()
|
protected open fun createGroup(): VisionGroupBase = VisionGroupBase()
|
||||||
@ -95,7 +100,7 @@ public open class VisionGroupBase : VisionBase(), MutableVisionGroup {
|
|||||||
/**
|
/**
|
||||||
* Set parent for given child and attach it
|
* Set parent for given child and attach it
|
||||||
*/
|
*/
|
||||||
private fun attach(token: NameToken, child: Vision) {
|
private fun attachChild(token: NameToken, child: Vision) {
|
||||||
if (child.parent == null) {
|
if (child.parent == null) {
|
||||||
child.parent = this
|
child.parent = this
|
||||||
childrenInternal[token] = child
|
childrenInternal[token] = child
|
||||||
@ -114,7 +119,7 @@ public open class VisionGroupBase : VisionBase(), MutableVisionGroup {
|
|||||||
val token = name.tokens.first()
|
val token = name.tokens.first()
|
||||||
when (val current = children[token]) {
|
when (val current = children[token]) {
|
||||||
null -> createGroup().also { child ->
|
null -> createGroup().also { child ->
|
||||||
attach(token, child)
|
attachChild(token, child)
|
||||||
}
|
}
|
||||||
is VisionGroupBase -> current
|
is VisionGroupBase -> current
|
||||||
else -> error("Can't create group with name $name because it exists and not a group")
|
else -> error("Can't create group with name $name because it exists and not a group")
|
||||||
@ -137,12 +142,13 @@ public open class VisionGroupBase : VisionBase(), MutableVisionGroup {
|
|||||||
}
|
}
|
||||||
name.length == 1 -> {
|
name.length == 1 -> {
|
||||||
val token = name.tokens.first()
|
val token = name.tokens.first()
|
||||||
|
val before = children[token]
|
||||||
if (child == null) {
|
if (child == null) {
|
||||||
removeChild(token)
|
removeChild(token)
|
||||||
} else {
|
} else {
|
||||||
attach(token, child)
|
attachChild(token, child)
|
||||||
}
|
}
|
||||||
childrenChanged(token, child)
|
childrenChanged(token, before, child)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
//TODO add safety check
|
//TODO add safety check
|
||||||
@ -165,7 +171,7 @@ public open class VisionGroupBase : VisionBase(), MutableVisionGroup {
|
|||||||
when {
|
when {
|
||||||
child is NullVision -> removeChild(token)
|
child is NullVision -> removeChild(token)
|
||||||
children.containsKey(token) -> children[token]!!.update(child)
|
children.containsKey(token) -> children[token]!!.update(child)
|
||||||
else -> attach(token, child)
|
else -> attachChild(token, child)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,31 +48,6 @@ public class VisionManager(meta: Meta) : AbstractPlugin(meta) {
|
|||||||
encodeToJsonElement(vision).toMetaItem(descriptor).node
|
encodeToJsonElement(vision).toMetaItem(descriptor).node
|
||||||
?: error("Expected node, but value found. Check your serializer!")
|
?: error("Expected node, but value found. Check your serializer!")
|
||||||
|
|
||||||
// public fun updateVision(vision: Vision, meta: Meta) {
|
|
||||||
// vision.update(meta)
|
|
||||||
// if (vision is MutableVisionGroup) {
|
|
||||||
// val children by meta.node()
|
|
||||||
// children?.items?.forEach { (token, item) ->
|
|
||||||
// when {
|
|
||||||
// item.value == Null -> vision[token] = null //Null means removal
|
|
||||||
// item.node != null -> {
|
|
||||||
// val node = item.node!!
|
|
||||||
// val type by node.string()
|
|
||||||
// if (type != null) {
|
|
||||||
// //If the type is present considering it as new node, not an update
|
|
||||||
// vision[token.asName()] = decodeFromMeta(node)
|
|
||||||
// } else {
|
|
||||||
// val existing = vision.children[token]
|
|
||||||
// if (existing != null) {
|
|
||||||
// updateVision(existing, node)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
public companion object : PluginFactory<VisionManager> {
|
public companion object : PluginFactory<VisionManager> {
|
||||||
override val tag: PluginTag = PluginTag(name = "vision", group = PluginTag.DATAFORGE_GROUP)
|
override val tag: PluginTag = PluginTag(name = "vision", group = PluginTag.DATAFORGE_GROUP)
|
||||||
override val type: KClass<out VisionManager> = VisionManager::class
|
override val type: KClass<out VisionManager> = VisionManager::class
|
||||||
|
@ -1,106 +0,0 @@
|
|||||||
package hep.dataforge.vision
|
|
||||||
|
|
||||||
import hep.dataforge.meta.*
|
|
||||||
import hep.dataforge.meta.descriptors.NodeDescriptor
|
|
||||||
import hep.dataforge.names.Name
|
|
||||||
import hep.dataforge.names.NameToken
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.flow
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.serialization.SerialName
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import kotlin.time.Duration
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An empty vision existing only for Vision tree change representation. [NullVision] should not be used outside update logic.
|
|
||||||
*/
|
|
||||||
@Serializable
|
|
||||||
@SerialName("vision.null")
|
|
||||||
public object NullVision : Vision {
|
|
||||||
|
|
||||||
@Suppress("SetterBackingFieldAssignment")
|
|
||||||
override var parent: VisionGroup? = null
|
|
||||||
set(value) {
|
|
||||||
//do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
override val properties: Config? = null
|
|
||||||
|
|
||||||
override fun getAllProperties(): Laminate = Laminate(Meta.EMPTY)
|
|
||||||
|
|
||||||
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? = null
|
|
||||||
|
|
||||||
override fun propertyChanged(name: Name) {}
|
|
||||||
|
|
||||||
override fun onPropertyChange(owner: Any?, action: (Name) -> Unit) {}
|
|
||||||
|
|
||||||
override fun removeChangeListener(owner: Any?) {}
|
|
||||||
|
|
||||||
override fun update(change: Vision) {
|
|
||||||
error("Null vision should be removed, not updated")
|
|
||||||
}
|
|
||||||
|
|
||||||
override val config: Config get() = Config()
|
|
||||||
override val descriptor: NodeDescriptor? get() = null
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Vision.collectChange(scope: CoroutineScope, collector: Vision): Job = scope.launch {
|
|
||||||
//Store job to be able to cancel collection jobs
|
|
||||||
//TODO add lock for concurrent modification protection?
|
|
||||||
val jobStore = HashMap<NameToken, Job>()
|
|
||||||
|
|
||||||
if (this is VisionGroup) {
|
|
||||||
check(collector is MutableVisionGroup) { "Collector for a group should be a group" }
|
|
||||||
//Subscribe for children changes
|
|
||||||
children.forEach { (token, child) ->
|
|
||||||
val childCollector: Vision = if (child is VisionGroup) {
|
|
||||||
VisionGroupBase()
|
|
||||||
} else {
|
|
||||||
VisionBase()
|
|
||||||
}
|
|
||||||
val job = child.collectChange(this, childCollector)
|
|
||||||
jobStore[token] = job
|
|
||||||
//TODO add lazy child addition
|
|
||||||
collector[token] = childCollector
|
|
||||||
}
|
|
||||||
|
|
||||||
//Subscribe for structure change
|
|
||||||
if (this is MutableVisionGroup) {
|
|
||||||
onChildrenChange(this) { token, child ->
|
|
||||||
//Cancel collector job to avoid leaking
|
|
||||||
jobStore[token]?.cancel()
|
|
||||||
if (child != null) {
|
|
||||||
//Collect to existing Vision
|
|
||||||
val job = child.collectChange(this, child)
|
|
||||||
jobStore[token] = job
|
|
||||||
collector[token] = child
|
|
||||||
} else{
|
|
||||||
collector[token] = NullVision
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Collect properties change
|
|
||||||
collector.onPropertyChange(collector) { propertyName ->
|
|
||||||
collector.config[propertyName] = properties?.get(propertyName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun Vision.flowChanges(scope: CoroutineScope, collectionDuration: Duration): Flow<Vision> = flow {
|
|
||||||
//emit initial visual tree
|
|
||||||
emit(this@flowChanges)
|
|
||||||
while (true) {
|
|
||||||
val collector: Vision = if (this is VisionGroup) {
|
|
||||||
VisionGroupBase()
|
|
||||||
} else {
|
|
||||||
VisionBase()
|
|
||||||
}
|
|
||||||
val collectorJob = collectChange(scope, collector)
|
|
||||||
kotlinx.coroutines.delay(collectionDuration)
|
|
||||||
emit(collector)
|
|
||||||
collectorJob.cancel()
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,141 @@
|
|||||||
|
package hep.dataforge.vision
|
||||||
|
|
||||||
|
import hep.dataforge.meta.*
|
||||||
|
import hep.dataforge.meta.descriptors.NodeDescriptor
|
||||||
|
import hep.dataforge.names.Name
|
||||||
|
import hep.dataforge.names.isEmpty
|
||||||
|
import hep.dataforge.names.plus
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
import kotlinx.serialization.*
|
||||||
|
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||||
|
import kotlinx.serialization.encoding.Decoder
|
||||||
|
import kotlinx.serialization.encoding.Encoder
|
||||||
|
import kotlin.time.Duration
|
||||||
|
|
||||||
|
|
||||||
|
public abstract class EmptyVision : Vision {
|
||||||
|
|
||||||
|
@Suppress("SetterBackingFieldAssignment", "UNUSED_PARAMETER")
|
||||||
|
override var parent: VisionGroup? = null
|
||||||
|
set(value) {
|
||||||
|
//do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
override val properties: Config? = null
|
||||||
|
|
||||||
|
override fun getAllProperties(): Laminate = Laminate(Meta.EMPTY)
|
||||||
|
|
||||||
|
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? = null
|
||||||
|
|
||||||
|
override fun propertyChanged(name: Name) {}
|
||||||
|
|
||||||
|
override fun onPropertyChange(owner: Any?, action: (Name) -> Unit) {}
|
||||||
|
|
||||||
|
override fun removeChangeListener(owner: Any?) {}
|
||||||
|
|
||||||
|
override fun update(change: Vision) {
|
||||||
|
error("Null vision should be removed, not updated")
|
||||||
|
}
|
||||||
|
|
||||||
|
override val config: Config get() = Config()
|
||||||
|
override val descriptor: NodeDescriptor? get() = null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An empty vision existing only for Vision tree change representation. [NullVision] should not be used outside update logic.
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
@SerialName("vision.null")
|
||||||
|
public object NullVision : EmptyVision()
|
||||||
|
|
||||||
|
@Serializable(VisionSerializationProxy.Companion::class)
|
||||||
|
private class VisionSerializationProxy(val ref: Vision) : EmptyVision() {
|
||||||
|
companion object : KSerializer<VisionSerializationProxy> {
|
||||||
|
override val descriptor: SerialDescriptor = Vision.serializer().descriptor
|
||||||
|
|
||||||
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
|
override fun serialize(encoder: Encoder, value: VisionSerializationProxy) {
|
||||||
|
val serializer = encoder.serializersModule.getPolymorphic(Vision::class, value.ref)
|
||||||
|
?: error("The polymorphic serializer is not provided for ")
|
||||||
|
serializer.serialize(encoder, value.ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deserialize(decoder: Decoder): VisionSerializationProxy =
|
||||||
|
VisionSerializationProxy(Vision.serializer().deserialize(decoder))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun MutableVisionGroup.getOrCreate(name: Name): Vision {
|
||||||
|
if (name.isEmpty()) return this
|
||||||
|
val existing = get(name)
|
||||||
|
return existing ?: VisionGroupBase().also { set(name, it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun CoroutineScope.collectChange(
|
||||||
|
name: Name,
|
||||||
|
source: Vision,
|
||||||
|
mutex: Mutex,
|
||||||
|
target: () -> MutableVisionGroup,
|
||||||
|
) {
|
||||||
|
val targetVision: () -> Vision = { target().getOrCreate(name) }
|
||||||
|
//Collect properties change
|
||||||
|
source.onPropertyChange(mutex) { propertyName ->
|
||||||
|
launch {
|
||||||
|
mutex.withLock {
|
||||||
|
targetVision().setProperty(propertyName, source.getProperty(propertyName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (source is VisionGroup) {
|
||||||
|
check(targetVision is MutableVisionGroup) { "Collector for a group should be a group" }
|
||||||
|
//Subscribe for children changes
|
||||||
|
source.children.forEach { (token, child) ->
|
||||||
|
collectChange(name + token, child, mutex, target)
|
||||||
|
}
|
||||||
|
//TODO update styles?
|
||||||
|
|
||||||
|
//Subscribe for structure change
|
||||||
|
if (source is MutableVisionGroup) {
|
||||||
|
source.onStructureChange(mutex) { token, before, after ->
|
||||||
|
before?.removeChangeListener(mutex)
|
||||||
|
(before as? MutableVisionGroup)?.removeStructureChangeListener(mutex)
|
||||||
|
if (after != null) {
|
||||||
|
targetVision[token] = VisionSerializationProxy(after)
|
||||||
|
collectChange(name + token, after, mutex, target)
|
||||||
|
} else {
|
||||||
|
targetVision[token] = NullVision
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@DFExperimental
|
||||||
|
public fun Vision.flowChanges(scope: CoroutineScope, collectionDuration: Duration): Flow<Vision> = flow {
|
||||||
|
//emit initial visual tree
|
||||||
|
emit(this@flowChanges)
|
||||||
|
|
||||||
|
val mutex = Mutex()
|
||||||
|
|
||||||
|
var collector = VisionGroupBase()
|
||||||
|
scope.collectChange(Name.EMPTY, this@flowChanges, mutex) { collector }
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
//Wait for changes to accumulate
|
||||||
|
kotlinx.coroutines.delay(collectionDuration)
|
||||||
|
//Propagate updates only if something is changed
|
||||||
|
if (collector.children.isNotEmpty() || collector.properties?.isEmpty() != false) {
|
||||||
|
//emit changes
|
||||||
|
emit(collector)
|
||||||
|
//Reset the collector
|
||||||
|
collector = VisionGroupBase()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package hep.dataforge.vision.rendering
|
||||||
|
|
||||||
|
import hep.dataforge.vision.Renderer
|
||||||
|
import hep.dataforge.vision.Vision
|
||||||
|
import org.w3c.dom.HTMLElement
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A display container factory for specific vision
|
||||||
|
* @param V type of [Vision] to be rendered
|
||||||
|
* @param C the specific type of the container
|
||||||
|
*/
|
||||||
|
public fun interface HTMLVisionDisplay<in V : Vision, C : Renderer<V>> {
|
||||||
|
public fun attachRenderer(element: HTMLElement): C
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a specific element and return container for configuration
|
||||||
|
*/
|
||||||
|
public fun <V : Vision, C : Renderer<V>> HTMLVisionDisplay<V, C>.render(element: HTMLElement, vision: V): C =
|
||||||
|
attachRenderer(element).apply { render(vision)}
|
@ -27,7 +27,7 @@ import tornadofx.*
|
|||||||
*
|
*
|
||||||
* @author [Alexander Nozik](mailto:altavir@gmail.com)
|
* @author [Alexander Nozik](mailto:altavir@gmail.com)
|
||||||
*/
|
*/
|
||||||
interface ValueChooser {
|
public interface ValueChooser {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get or create a Node that could be later inserted into some parent
|
* Get or create a Node that could be later inserted into some parent
|
||||||
|
@ -54,9 +54,9 @@ private class GDMLTransformer(val settings: GDMLTransformerSettings) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private val referenceStore = HashMap<Name, MutableList<Proxy>>()
|
private val referenceStore = HashMap<Name, MutableList<SolidReference>>()
|
||||||
|
|
||||||
private fun proxySolid(root: GDML, group: SolidGroup, solid: GDMLSolid, name: String): Proxy {
|
private fun proxySolid(root: GDML, group: SolidGroup, solid: GDMLSolid, name: String): SolidReference {
|
||||||
val templateName = solidsName + name
|
val templateName = solidsName + name
|
||||||
if (proto[templateName] == null) {
|
if (proto[templateName] == null) {
|
||||||
solids.addSolid(root, solid, name)
|
solids.addSolid(root, solid, name)
|
||||||
@ -66,7 +66,7 @@ private class GDMLTransformer(val settings: GDMLTransformerSettings) {
|
|||||||
return ref
|
return ref
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun proxyVolume(root: GDML, group: SolidGroup, physVolume: GDMLPhysVolume, volume: GDMLGroup): Proxy {
|
private fun proxyVolume(root: GDML, group: SolidGroup, physVolume: GDMLPhysVolume, volume: GDMLGroup): SolidReference {
|
||||||
val templateName = volumesName + volume.name.asName()
|
val templateName = volumesName + volume.name.asName()
|
||||||
if (proto[templateName] == null) {
|
if (proto[templateName] == null) {
|
||||||
proto[templateName] = volume(root, volume)
|
proto[templateName] = volume(root, volume)
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
package hep.dataforge.vision.gdml
|
package hep.dataforge.vision.gdml
|
||||||
|
|
||||||
import hep.dataforge.vision.solid.AbstractProxy
|
|
||||||
import hep.dataforge.vision.solid.prototype
|
import hep.dataforge.vision.solid.prototype
|
||||||
import hep.dataforge.vision.visitor.countDistinct
|
import hep.dataforge.vision.visitor.countDistinct
|
||||||
import hep.dataforge.vision.visitor.countDistinctBy
|
|
||||||
import hep.dataforge.vision.visitor.flowStatistics
|
import hep.dataforge.vision.visitor.flowStatistics
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import nl.adaptivity.xmlutil.StAXReader
|
|
||||||
import kscience.gdml.GDML
|
import kscience.gdml.GDML
|
||||||
|
import nl.adaptivity.xmlutil.StAXReader
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
@ -6,3 +6,10 @@ plugins {
|
|||||||
|
|
||||||
val ktorVersion: String by rootProject.extra
|
val ktorVersion: String by rootProject.extra
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
api(project(":visionforge-core"))
|
||||||
|
api("io.ktor:ktor-server-cio:$ktorVersion")
|
||||||
|
//api("io.ktor:ktor-server-netty:$ktorVersion")
|
||||||
|
api("io.ktor:ktor-html-builder:$ktorVersion")
|
||||||
|
api("io.ktor:ktor-websockets:$ktorVersion")
|
||||||
|
}
|
@ -0,0 +1,219 @@
|
|||||||
|
package hep.dataforge.vision.server
|
||||||
|
|
||||||
|
import hep.dataforge.meta.*
|
||||||
|
import hep.dataforge.names.Name
|
||||||
|
import hep.dataforge.names.toName
|
||||||
|
import hep.dataforge.vision.server.VisionServer.Companion.DEFAULT_PAGE
|
||||||
|
import io.ktor.application.Application
|
||||||
|
import io.ktor.application.featureOrNull
|
||||||
|
import io.ktor.application.install
|
||||||
|
import io.ktor.features.CORS
|
||||||
|
import io.ktor.http.content.resources
|
||||||
|
import io.ktor.http.content.static
|
||||||
|
import io.ktor.routing.Routing
|
||||||
|
import io.ktor.routing.route
|
||||||
|
import io.ktor.routing.routing
|
||||||
|
import io.ktor.server.engine.ApplicationEngine
|
||||||
|
import io.ktor.websocket.WebSockets
|
||||||
|
import kotlinx.html.TagConsumer
|
||||||
|
import java.awt.Desktop
|
||||||
|
import java.net.URI
|
||||||
|
import kotlin.text.get
|
||||||
|
|
||||||
|
public enum class ServerUpdateMode {
|
||||||
|
NONE,
|
||||||
|
PUSH,
|
||||||
|
PULL
|
||||||
|
}
|
||||||
|
|
||||||
|
public class VisionServer internal constructor(
|
||||||
|
private val routing: Routing,
|
||||||
|
private val rootRoute: String,
|
||||||
|
) : Configurable {
|
||||||
|
override val config: Config = Config()
|
||||||
|
public var updateMode: ServerUpdateMode by config.enum(ServerUpdateMode.NONE, key = UPDATE_MODE_KEY)
|
||||||
|
public var updateInterval: Long by config.long(300, key = UPDATE_INTERVAL_KEY)
|
||||||
|
public var embedData: Boolean by config.boolean(false)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* a list of headers that should be applied to all pages
|
||||||
|
*/
|
||||||
|
private val globalHeaders: ArrayList<HtmlFragment> = ArrayList<HtmlFragment>()
|
||||||
|
|
||||||
|
public fun header(block: TagConsumer<*>.() -> Unit) {
|
||||||
|
globalHeaders.add(HtmlFragment(block))
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun page(
|
||||||
|
plotlyFragment: PlotlyFragment,
|
||||||
|
route: String = DEFAULT_PAGE,
|
||||||
|
title: String = "Plotly server page '$route'",
|
||||||
|
headers: List<HtmlFragment> = emptyList(),
|
||||||
|
) {
|
||||||
|
routing.createRouteFromPath(rootRoute).apply {
|
||||||
|
val plots = HashMap<String, Plot>()
|
||||||
|
route(route) {
|
||||||
|
//Update websocket
|
||||||
|
webSocket("ws/{id}") {
|
||||||
|
val plotId: String = call.parameters["id"] ?: error("Plot id not defined")
|
||||||
|
|
||||||
|
application.log.debug("Opened server socket for $plotId")
|
||||||
|
|
||||||
|
val plot = plots[plotId] ?: error("Plot with id='$plotId' not registered")
|
||||||
|
|
||||||
|
try {
|
||||||
|
plot.collectUpdates(plotId, this, updateInterval).collect { update ->
|
||||||
|
val json = update.toJson()
|
||||||
|
outgoing.send(Frame.Text(json.toString()))
|
||||||
|
}
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
application.log.debug("Closed server socket for $plotId")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Plots in their json representation
|
||||||
|
get("data/{id}") {
|
||||||
|
val id: String = call.parameters["id"] ?: error("Plot id not defined")
|
||||||
|
|
||||||
|
val plot: Plot? = plots[id]
|
||||||
|
if (plot == null) {
|
||||||
|
call.respond(HttpStatusCode.NotFound, "Plot with id = $id not found")
|
||||||
|
} else {
|
||||||
|
call.respondText(
|
||||||
|
plot.toJsonString(),
|
||||||
|
contentType = ContentType.Application.Json,
|
||||||
|
status = HttpStatusCode.OK
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//filled pages
|
||||||
|
get {
|
||||||
|
val origin = call.request.origin
|
||||||
|
val url = URLBuilder().apply {
|
||||||
|
protocol = URLProtocol.createOrDefault(origin.scheme)
|
||||||
|
//workaround for https://github.com/ktorio/ktor/issues/1663
|
||||||
|
host = if (origin.host.startsWith("0:")) "[${origin.host}]" else origin.host
|
||||||
|
port = origin.port
|
||||||
|
encodedPath = origin.uri
|
||||||
|
}.build()
|
||||||
|
call.respondHtml {
|
||||||
|
val normalizedRoute = if (rootRoute.endsWith("/")) {
|
||||||
|
rootRoute
|
||||||
|
} else {
|
||||||
|
"$rootRoute/"
|
||||||
|
}
|
||||||
|
|
||||||
|
head {
|
||||||
|
meta {
|
||||||
|
charset = "utf-8"
|
||||||
|
(globalHeaders + headers).forEach {
|
||||||
|
it.visit(consumer)
|
||||||
|
}
|
||||||
|
script {
|
||||||
|
type = "text/javascript"
|
||||||
|
src = "${normalizedRoute}js/plotly.min.js"
|
||||||
|
}
|
||||||
|
script {
|
||||||
|
type = "text/javascript"
|
||||||
|
src = "${normalizedRoute}js/plotlyConnect.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
title(title)
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
val container =
|
||||||
|
ServerPlotlyRenderer(url, updateMode, updateInterval, embedData) { plotId, plot ->
|
||||||
|
plots[plotId] = plot
|
||||||
|
}
|
||||||
|
with(plotlyFragment) {
|
||||||
|
render(container)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun page(
|
||||||
|
route: String = DEFAULT_PAGE,
|
||||||
|
title: String = "Plotly server page '$route'",
|
||||||
|
headers: List<HtmlFragment> = emptyList(),
|
||||||
|
content: FlowContent.(renderer: PlotlyRenderer) -> Unit,
|
||||||
|
) {
|
||||||
|
page(PlotlyFragment(content), route, title, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public companion object {
|
||||||
|
public const val DEFAULT_PAGE: String = "/"
|
||||||
|
public val UPDATE_MODE_KEY: Name = "update.mode".toName()
|
||||||
|
public val UPDATE_INTERVAL_KEY: Name = "update.interval".toName()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attach plotly application to given server
|
||||||
|
*/
|
||||||
|
public fun Application.visionModule(route: String = DEFAULT_PAGE): VisionServer {
|
||||||
|
if (featureOrNull(WebSockets) == null) {
|
||||||
|
install(WebSockets)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (featureOrNull(CORS) == null) {
|
||||||
|
install(CORS) {
|
||||||
|
anyHost()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val routing = routing {
|
||||||
|
route(route) {
|
||||||
|
static {
|
||||||
|
resources()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return VisionServer(routing, route)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure server to start sending updates in push mode. Does not affect loaded pages
|
||||||
|
*/
|
||||||
|
public fun VisionServer.pushUpdates(interval: Long = 100): VisionServer = apply {
|
||||||
|
updateMode = ServerUpdateMode.PUSH
|
||||||
|
updateInterval = interval
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure client to request regular updates from server. Pull updates are more expensive than push updates since
|
||||||
|
* they contain the full plot data and server can't decide what to send.
|
||||||
|
*/
|
||||||
|
public fun VisionServer.pullUpdates(interval: Long = 1000): VisionServer = apply {
|
||||||
|
updateMode = ServerUpdateMode.PULL
|
||||||
|
updateInterval = interval
|
||||||
|
}
|
||||||
|
|
||||||
|
///**
|
||||||
|
// * Start static server (updates via reload)
|
||||||
|
// */
|
||||||
|
//@OptIn(KtorExperimentalAPI::class)
|
||||||
|
//public fun Plotly.serve(
|
||||||
|
// scope: CoroutineScope = GlobalScope,
|
||||||
|
// host: String = "localhost",
|
||||||
|
// port: Int = 7777,
|
||||||
|
// block: PlotlyServer.() -> Unit,
|
||||||
|
//): ApplicationEngine = scope.embeddedServer(io.ktor.server.cio.CIO, port, host) {
|
||||||
|
// plotlyModule().apply(block)
|
||||||
|
//}.start()
|
||||||
|
|
||||||
|
|
||||||
|
public fun ApplicationEngine.show() {
|
||||||
|
val connector = environment.connectors.first()
|
||||||
|
val uri = URI("http", null, connector.host, connector.port, null, null, null)
|
||||||
|
Desktop.getDesktop().browse(uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun ApplicationEngine.close(): Unit = stop(1000, 5000)
|
@ -0,0 +1,70 @@
|
|||||||
|
package hep.dataforge.vision.server
|
||||||
|
|
||||||
|
import kotlinx.html.*
|
||||||
|
import kotlinx.html.stream.createHTML
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.nio.file.StandardOpenOption
|
||||||
|
|
||||||
|
public class HtmlFragment(public val visit: TagConsumer<*>.() -> Unit) {
|
||||||
|
override fun toString(): String {
|
||||||
|
return createHTML().also(visit).finalize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public operator fun HtmlFragment.plus(other: HtmlFragment): HtmlFragment = HtmlFragment {
|
||||||
|
this@plus.run { visit() }
|
||||||
|
other.run { visit() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the asset exists in given local location and put it there if it does not
|
||||||
|
*/
|
||||||
|
internal fun checkOrStoreFile(basePath: Path, filePath: Path, resource: String): Path {
|
||||||
|
val fullPath = basePath.resolveSibling(filePath).toAbsolutePath()
|
||||||
|
|
||||||
|
if (Files.exists(fullPath)) {
|
||||||
|
//TODO checksum
|
||||||
|
} else {
|
||||||
|
//TODO add logging
|
||||||
|
|
||||||
|
val bytes = HtmlFragment::class.java.getResourceAsStream(resource).readAllBytes()
|
||||||
|
Files.createDirectories(fullPath.parent)
|
||||||
|
Files.write(fullPath, bytes, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)
|
||||||
|
}
|
||||||
|
|
||||||
|
return if (basePath.isAbsolute && fullPath.startsWith(basePath)) {
|
||||||
|
basePath.relativize(fullPath)
|
||||||
|
} else {
|
||||||
|
filePath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A header that automatically copies relevant scripts to given path
|
||||||
|
*/
|
||||||
|
public fun localScriptHeader(
|
||||||
|
basePath: Path,
|
||||||
|
scriptPath: Path,
|
||||||
|
resource: String
|
||||||
|
): HtmlFragment = HtmlFragment {
|
||||||
|
val relativePath = checkOrStoreFile(basePath, scriptPath, resource)
|
||||||
|
script {
|
||||||
|
type = "text/javascript"
|
||||||
|
src = relativePath.toString()
|
||||||
|
attributes["onload"] = "console.log('Script successfully loaded from $relativePath')"
|
||||||
|
attributes["onerror"] = "console.log('Failed to load script from $relativePath')"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun localCssHeader(
|
||||||
|
basePath: Path,
|
||||||
|
cssPath: Path,
|
||||||
|
resource: String
|
||||||
|
): HtmlFragment = HtmlFragment {
|
||||||
|
val relativePath = checkOrStoreFile(basePath, cssPath, resource)
|
||||||
|
link {
|
||||||
|
rel = "stylesheet"
|
||||||
|
href = relativePath.toString()
|
||||||
|
}
|
||||||
|
}
|
@ -29,7 +29,7 @@ kotlin {
|
|||||||
}
|
}
|
||||||
jsMain {
|
jsMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(npm("three", "0.114.0"))
|
implementation(npm("three", "0.122.0"))
|
||||||
implementation(npm("three-csg-ts", "1.0.1"))
|
implementation(npm("three-csg-ts", "1.0.1"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,9 @@ import hep.dataforge.meta.descriptors.NodeDescriptor
|
|||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.Name
|
||||||
import hep.dataforge.names.asName
|
import hep.dataforge.names.asName
|
||||||
import hep.dataforge.names.plus
|
import hep.dataforge.names.plus
|
||||||
import hep.dataforge.output.Renderer
|
|
||||||
import hep.dataforge.values.ValueType
|
import hep.dataforge.values.ValueType
|
||||||
import hep.dataforge.values.asValue
|
import hep.dataforge.values.asValue
|
||||||
|
import hep.dataforge.vision.Renderer
|
||||||
import hep.dataforge.vision.Vision
|
import hep.dataforge.vision.Vision
|
||||||
import hep.dataforge.vision.Vision.Companion.VISIBLE_KEY
|
import hep.dataforge.vision.Vision.Companion.VISIBLE_KEY
|
||||||
import hep.dataforge.vision.enum
|
import hep.dataforge.vision.enum
|
||||||
@ -104,8 +104,7 @@ public var Solid.layer: Int
|
|||||||
config[LAYER_KEY] = value.asValue()
|
config[LAYER_KEY] = value.asValue()
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun Renderer<Solid>.render(meta: Meta = Meta.EMPTY, action: SolidGroup.() -> Unit): Unit =
|
public fun Renderer<Solid>.render(action: SolidGroup.() -> Unit): Unit = render(SolidGroup().apply(action))
|
||||||
render(SolidGroup().apply(action), meta)
|
|
||||||
|
|
||||||
// Common properties
|
// Common properties
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ public class SolidManager(meta: Meta) : AbstractPlugin(meta) {
|
|||||||
|
|
||||||
private fun PolymorphicModuleBuilder<Solid>.solids() {
|
private fun PolymorphicModuleBuilder<Solid>.solids() {
|
||||||
subclass(SolidGroup.serializer())
|
subclass(SolidGroup.serializer())
|
||||||
subclass(Proxy.serializer())
|
subclass(SolidReference.serializer())
|
||||||
subclass(Composite.serializer())
|
subclass(Composite.serializer())
|
||||||
subclass(Tube.serializer())
|
subclass(Tube.serializer())
|
||||||
subclass(Box.serializer())
|
subclass(Box.serializer())
|
||||||
|
@ -9,7 +9,7 @@ import kotlinx.serialization.Serializable
|
|||||||
import kotlinx.serialization.Transient
|
import kotlinx.serialization.Transient
|
||||||
import kotlin.collections.set
|
import kotlin.collections.set
|
||||||
|
|
||||||
public abstract class AbstractProxy : BasicSolid(), VisionGroup {
|
public abstract class AbstractReference : BasicSolid(), VisionGroup {
|
||||||
public abstract val prototype: Solid
|
public abstract val prototype: Solid
|
||||||
|
|
||||||
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? {
|
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? {
|
||||||
@ -46,13 +46,13 @@ public abstract class AbstractProxy : BasicSolid(), VisionGroup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A proxy [Solid] to reuse a template object
|
* A reference [Solid] to reuse a template object
|
||||||
*/
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
@SerialName("solid.proxy")
|
@SerialName("solid.ref")
|
||||||
public class Proxy(
|
public class SolidReference(
|
||||||
public val templateName: Name,
|
public val templateName: Name,
|
||||||
) : AbstractProxy(), Solid {
|
) : AbstractReference(), Solid {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recursively search for defined template in the parent
|
* Recursively search for defined template in the parent
|
||||||
@ -67,15 +67,15 @@ public class Proxy(
|
|||||||
private val propertyCache: HashMap<Name, Config> = HashMap()
|
private val propertyCache: HashMap<Name, Config> = HashMap()
|
||||||
|
|
||||||
|
|
||||||
override val children: Map<NameToken, Proxy.ProxyChild>
|
override val children: Map<NameToken, SolidReference.ReferenceChild>
|
||||||
get() = (prototype as? VisionGroup)?.children
|
get() = (prototype as? VisionGroup)?.children
|
||||||
?.filter { !it.key.toString().startsWith("@") }
|
?.filter { !it.key.toString().startsWith("@") }
|
||||||
?.mapValues {
|
?.mapValues {
|
||||||
ProxyChild(it.key.asName())
|
ReferenceChild(it.key.asName())
|
||||||
} ?: emptyMap()
|
} ?: emptyMap()
|
||||||
|
|
||||||
private fun childPropertyName(childName: Name, propertyName: Name): Name {
|
private fun childPropertyName(childName: Name, propertyName: Name): Name {
|
||||||
return NameToken(PROXY_CHILD_PROPERTY_PREFIX, childName.toString()) + propertyName
|
return NameToken(REFERENCE_CHILD_PROPERTY_PREFIX, childName.toString()) + propertyName
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun prototypeFor(name: Name): Solid {
|
private fun prototypeFor(name: Name): Solid {
|
||||||
@ -89,17 +89,17 @@ public class Proxy(
|
|||||||
* A ProxyChild is created temporarily only to interact with properties, it does not store any values
|
* A ProxyChild is created temporarily only to interact with properties, it does not store any values
|
||||||
* (properties are stored in external cache) and created and destroyed on-demand).
|
* (properties are stored in external cache) and created and destroyed on-demand).
|
||||||
*/
|
*/
|
||||||
public inner class ProxyChild(public val name: Name) : AbstractProxy() {
|
public inner class ReferenceChild(public val name: Name) : AbstractReference() {
|
||||||
|
|
||||||
override val prototype: Solid get() = prototypeFor(name)
|
override val prototype: Solid get() = prototypeFor(name)
|
||||||
|
|
||||||
override val styleSheet: StyleSheet get() = this@Proxy.styleSheet
|
override val styleSheet: StyleSheet get() = this@SolidReference.styleSheet
|
||||||
|
|
||||||
override val children: Map<NameToken, Vision>
|
override val children: Map<NameToken, Vision>
|
||||||
get() = (prototype as? VisionGroup)?.children
|
get() = (prototype as? VisionGroup)?.children
|
||||||
?.filter { !it.key.toString().startsWith("@") }
|
?.filter { !it.key.toString().startsWith("@") }
|
||||||
?.mapValues { (key, _) ->
|
?.mapValues { (key, _) ->
|
||||||
ProxyChild(name + key.asName())
|
ReferenceChild(name + key.asName())
|
||||||
} ?: emptyMap()
|
} ?: emptyMap()
|
||||||
|
|
||||||
override var properties: Config?
|
override var properties: Config?
|
||||||
@ -108,12 +108,12 @@ public class Proxy(
|
|||||||
if (value == null) {
|
if (value == null) {
|
||||||
propertyCache.remove(name)?.also {
|
propertyCache.remove(name)?.also {
|
||||||
//Removing listener if it is present
|
//Removing listener if it is present
|
||||||
removeChangeListener(this@Proxy)
|
removeChangeListener(this@SolidReference)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
propertyCache[name] = value.also {
|
propertyCache[name] = value.also {
|
||||||
onPropertyChange(this@Proxy) { propertyName ->
|
onPropertyChange(this@SolidReference) { propertyName ->
|
||||||
this@Proxy.propertyChanged(childPropertyName(name, propertyName))
|
this@SolidReference.propertyChanged(childPropertyName(name, propertyName))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -122,16 +122,16 @@ public class Proxy(
|
|||||||
}
|
}
|
||||||
|
|
||||||
public companion object {
|
public companion object {
|
||||||
public const val PROXY_CHILD_PROPERTY_PREFIX: String = "@child"
|
public const val REFERENCE_CHILD_PROPERTY_PREFIX: String = "@child"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a vision prototype if it is a [Proxy] or vision itself if it is not
|
* Get a vision prototype if it is a [SolidReference] or vision itself if it is not
|
||||||
*/
|
*/
|
||||||
public val Vision.prototype: Vision
|
public val Vision.prototype: Vision
|
||||||
get() = when (this) {
|
get() = when (this) {
|
||||||
is AbstractProxy -> prototype
|
is AbstractReference -> prototype
|
||||||
else -> this
|
else -> this
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,16 +141,16 @@ public val Vision.prototype: Vision
|
|||||||
public fun SolidGroup.ref(
|
public fun SolidGroup.ref(
|
||||||
templateName: Name,
|
templateName: Name,
|
||||||
name: String = "",
|
name: String = "",
|
||||||
): Proxy = Proxy(templateName).also { set(name, it) }
|
): SolidReference = SolidReference(templateName).also { set(name, it) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add new proxy wrapping given object and automatically adding it to the prototypes
|
* Add new [SolidReference] wrapping given object and automatically adding it to the prototypes
|
||||||
*/
|
*/
|
||||||
public fun SolidGroup.proxy(
|
public fun SolidGroup.ref(
|
||||||
name: String,
|
name: String,
|
||||||
obj: Solid,
|
obj: Solid,
|
||||||
templateName: Name = name.toName(),
|
templateName: Name = name.toName(),
|
||||||
): Proxy {
|
): SolidReference {
|
||||||
val existing = getPrototype(templateName)
|
val existing = getPrototype(templateName)
|
||||||
if (existing == null) {
|
if (existing == null) {
|
||||||
prototypes {
|
prototypes {
|
||||||
@ -159,5 +159,5 @@ public fun SolidGroup.proxy(
|
|||||||
} 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)
|
return this@ref.ref(templateName, name)
|
||||||
}
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package hep.dataforge.vision.solid.specifications
|
package hep.dataforge.vision.solid.specifications
|
||||||
|
|
||||||
import hep.dataforge.meta.*
|
import hep.dataforge.meta.*
|
||||||
|
import hep.dataforge.names.Name
|
||||||
|
|
||||||
public class Canvas3DOptions : Scheme() {
|
public class Canvas3DOptions : Scheme() {
|
||||||
public var axes: Axes by spec(Axes, Axes.empty())
|
public var axes: Axes by spec(Axes, Axes.empty())
|
||||||
@ -15,6 +16,8 @@ public class Canvas3DOptions : Scheme() {
|
|||||||
public var maxWith: Number by number { maxSize }
|
public var maxWith: Number by number { maxSize }
|
||||||
public var maxHeight: Number by number { maxSize }
|
public var maxHeight: Number by number { maxSize }
|
||||||
|
|
||||||
|
public var onSelect: ((Name?)->Unit)? = null
|
||||||
|
|
||||||
|
|
||||||
public companion object : SchemeSpec<Canvas3DOptions>(::Canvas3DOptions)
|
public companion object : SchemeSpec<Canvas3DOptions>(::Canvas3DOptions)
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ internal object RemoveSingleChild : VisualTreeTransform<SolidGroup>() {
|
|||||||
override fun SolidGroup.transformInPlace() {
|
override fun SolidGroup.transformInPlace() {
|
||||||
fun MutableVisionGroup.replaceChildren() {
|
fun MutableVisionGroup.replaceChildren() {
|
||||||
children.forEach { (childName, parent) ->
|
children.forEach { (childName, parent) ->
|
||||||
if (parent is Proxy) return@forEach //ignore refs
|
if (parent is SolidReference) return@forEach //ignore refs
|
||||||
if (parent is MutableVisionGroup) {
|
if (parent is MutableVisionGroup) {
|
||||||
parent.replaceChildren()
|
parent.replaceChildren()
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,8 @@ import hep.dataforge.names.Name
|
|||||||
import hep.dataforge.names.asName
|
import hep.dataforge.names.asName
|
||||||
import hep.dataforge.vision.MutableVisionGroup
|
import hep.dataforge.vision.MutableVisionGroup
|
||||||
import hep.dataforge.vision.VisionGroup
|
import hep.dataforge.vision.VisionGroup
|
||||||
import hep.dataforge.vision.solid.Proxy
|
|
||||||
import hep.dataforge.vision.solid.SolidGroup
|
import hep.dataforge.vision.solid.SolidGroup
|
||||||
|
import hep.dataforge.vision.solid.SolidReference
|
||||||
|
|
||||||
@DFExperimental
|
@DFExperimental
|
||||||
internal object UnRef : VisualTreeTransform<SolidGroup>() {
|
internal object UnRef : VisualTreeTransform<SolidGroup>() {
|
||||||
@ -17,7 +17,7 @@ internal object UnRef : VisualTreeTransform<SolidGroup>() {
|
|||||||
counter.forEach { (key, value) ->
|
counter.forEach { (key, value) ->
|
||||||
reducer[key] = (reducer[key] ?: 0) + value
|
reducer[key] = (reducer[key] ?: 0) + value
|
||||||
}
|
}
|
||||||
} else if (obj is Proxy) {
|
} else if (obj is SolidReference) {
|
||||||
reducer[obj.templateName] = (reducer[obj.templateName] ?: 0) + 1
|
reducer[obj.templateName] = (reducer[obj.templateName] ?: 0) + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,9 +27,9 @@ internal object UnRef : VisualTreeTransform<SolidGroup>() {
|
|||||||
|
|
||||||
private fun MutableVisionGroup.unref(name: Name) {
|
private fun MutableVisionGroup.unref(name: Name) {
|
||||||
(this as? SolidGroup)?.prototypes?.set(name, null)
|
(this as? SolidGroup)?.prototypes?.set(name, null)
|
||||||
children.filter { (it.value as? Proxy)?.templateName == name }.forEach { (key, value) ->
|
children.filter { (it.value as? SolidReference)?.templateName == name }.forEach { (key, value) ->
|
||||||
val proxy = value as Proxy
|
val reference = value as SolidReference
|
||||||
val newChild = mergeChild(proxy, proxy.prototype)
|
val newChild = mergeChild(reference, reference.prototype)
|
||||||
newChild.parent = null
|
newChild.parent = null
|
||||||
set(key.asName(), newChild) // replace proxy with merged object
|
set(key.asName(), newChild) // replace proxy with merged object
|
||||||
}
|
}
|
||||||
|
@ -58,8 +58,8 @@ class PropertyTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testProxyStyleProperty() {
|
fun testReferenceStyleProperty() {
|
||||||
var box: Proxy? = null
|
var box: SolidReference? = null
|
||||||
val group = SolidGroup{
|
val group = SolidGroup{
|
||||||
styleSheet {
|
styleSheet {
|
||||||
set("testStyle") {
|
set("testStyle") {
|
||||||
|
@ -3,7 +3,6 @@ package hep.dataforge.vision.solid
|
|||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.Name
|
||||||
import hep.dataforge.names.toName
|
import hep.dataforge.names.toName
|
||||||
import hep.dataforge.vision.MutableVisionGroup
|
import hep.dataforge.vision.MutableVisionGroup
|
||||||
import hep.dataforge.vision.Vision
|
|
||||||
import hep.dataforge.vision.get
|
import hep.dataforge.vision.get
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
@ -12,13 +11,13 @@ import kotlin.test.assertEquals
|
|||||||
/**
|
/**
|
||||||
* Create and attach new proxied group
|
* Create and attach new proxied group
|
||||||
*/
|
*/
|
||||||
fun SolidGroup.proxyGroup(
|
fun SolidGroup.refGroup(
|
||||||
name: String,
|
name: String,
|
||||||
templateName: Name = name.toName(),
|
templateName: Name = name.toName(),
|
||||||
block: MutableVisionGroup.() -> Unit
|
block: MutableVisionGroup.() -> Unit
|
||||||
): Proxy {
|
): SolidReference {
|
||||||
val group = SolidGroup().apply(block)
|
val group = SolidGroup().apply(block)
|
||||||
return proxy(name, group, templateName)
|
return ref(name, group, templateName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -44,8 +43,8 @@ class SerializationTest {
|
|||||||
z = -100
|
z = -100
|
||||||
}
|
}
|
||||||
val group = SolidGroup{
|
val group = SolidGroup{
|
||||||
proxy("cube", cube)
|
ref("cube", cube)
|
||||||
proxyGroup("pg", "pg.content".toName()){
|
refGroup("pg", "pg.content".toName()){
|
||||||
sphere(50){
|
sphere(50){
|
||||||
x = -100
|
x = -100
|
||||||
}
|
}
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
package hep.dataforge.vision.solid.three
|
|
||||||
|
|
||||||
fun createDataControls() {
|
|
||||||
val dat = kotlinext.js.require("dat.gui")
|
|
||||||
}
|
|
@ -1,12 +1,12 @@
|
|||||||
package hep.dataforge.vision.solid.three
|
package hep.dataforge.vision.solid.three
|
||||||
|
|
||||||
import hep.dataforge.context.Context
|
import hep.dataforge.meta.getItem
|
||||||
import hep.dataforge.meta.*
|
import hep.dataforge.meta.string
|
||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.Name
|
||||||
import hep.dataforge.names.plus
|
import hep.dataforge.names.plus
|
||||||
import hep.dataforge.names.toName
|
import hep.dataforge.names.toName
|
||||||
import hep.dataforge.output.Renderer
|
|
||||||
import hep.dataforge.vision.Colors
|
import hep.dataforge.vision.Colors
|
||||||
|
import hep.dataforge.vision.Renderer
|
||||||
import hep.dataforge.vision.solid.Solid
|
import hep.dataforge.vision.solid.Solid
|
||||||
import hep.dataforge.vision.solid.specifications.*
|
import hep.dataforge.vision.solid.specifications.*
|
||||||
import hep.dataforge.vision.solid.three.ThreeMaterials.HIGHLIGHT_MATERIAL
|
import hep.dataforge.vision.solid.three.ThreeMaterials.HIGHLIGHT_MATERIAL
|
||||||
@ -41,28 +41,24 @@ import kotlin.math.sin
|
|||||||
public class ThreeCanvas(
|
public class ThreeCanvas(
|
||||||
public val three: ThreePlugin,
|
public val three: ThreePlugin,
|
||||||
public val options: Canvas3DOptions,
|
public val options: Canvas3DOptions,
|
||||||
public val onClick: ((Name?) -> Unit)? = null,
|
|
||||||
) : Renderer<Solid> {
|
) : Renderer<Solid> {
|
||||||
|
|
||||||
override val context: Context get() = three.context
|
|
||||||
|
|
||||||
public var content: Solid? = null
|
|
||||||
private set
|
|
||||||
|
|
||||||
private var root: Object3D? = null
|
private var root: Object3D? = null
|
||||||
|
|
||||||
private val raycaster = Raycaster()
|
private val raycaster = Raycaster()
|
||||||
private val mousePosition: Vector2 = Vector2()
|
private val mousePosition: Vector2 = Vector2()
|
||||||
|
|
||||||
public val axes: AxesHelper = AxesHelper(options.axes.size.toInt()).apply {
|
public var content: Solid? = null
|
||||||
visible = options.axes.visible
|
private set
|
||||||
}
|
|
||||||
|
|
||||||
public val scene: Scene = Scene().apply {
|
public var axes: AxesHelper = AxesHelper(options.axes.size.toInt()).apply { visible = options.axes.visible }
|
||||||
|
private set
|
||||||
|
|
||||||
|
private val scene: Scene = Scene().apply {
|
||||||
add(axes)
|
add(axes)
|
||||||
}
|
}
|
||||||
|
|
||||||
public val camera: PerspectiveCamera = buildCamera(options.camera)
|
public var camera: PerspectiveCamera = buildCamera(options.camera)
|
||||||
|
private set
|
||||||
|
|
||||||
private var picked: Object3D? = null
|
private var picked: Object3D? = null
|
||||||
|
|
||||||
@ -97,7 +93,7 @@ public class ThreeCanvas(
|
|||||||
|
|
||||||
element.addEventListener("mousedown", {
|
element.addEventListener("mousedown", {
|
||||||
val picked = pick()
|
val picked = pick()
|
||||||
onClick?.invoke(picked?.fullName())
|
options.onSelect?.invoke(picked?.fullName())
|
||||||
}, false)
|
}, false)
|
||||||
|
|
||||||
val renderer = WebGLRenderer { antialias = true }.apply {
|
val renderer = WebGLRenderer { antialias = true }.apply {
|
||||||
@ -193,15 +189,14 @@ public class ThreeCanvas(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun render(obj: Solid, meta: Meta) {
|
public override fun render(vision: Solid) {
|
||||||
//clear old root
|
//clear old root
|
||||||
clear()
|
clear()
|
||||||
|
|
||||||
|
val object3D = three.buildObject3D(vision)
|
||||||
val object3D = three.buildObject3D(obj)
|
|
||||||
object3D.name = "@root"
|
object3D.name = "@root"
|
||||||
scene.add(object3D)
|
scene.add(object3D)
|
||||||
content = obj
|
content = vision
|
||||||
root = object3D
|
root = object3D
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -261,16 +256,3 @@ public class ThreeCanvas(
|
|||||||
private const val SELECT_NAME = "@select"
|
private const val SELECT_NAME = "@select"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun ThreePlugin.output(
|
|
||||||
element: HTMLElement,
|
|
||||||
spec: Canvas3DOptions = Canvas3DOptions.empty(),
|
|
||||||
onClick: ((Name?) -> Unit)? = null,
|
|
||||||
): ThreeCanvas = ThreeCanvas(this, spec, onClick).apply { attach(element) }
|
|
||||||
|
|
||||||
public fun ThreePlugin.render(
|
|
||||||
element: HTMLElement,
|
|
||||||
obj: Solid,
|
|
||||||
onSelect: ((Name?) -> Unit)? = null,
|
|
||||||
options: Canvas3DOptions.() -> Unit = {},
|
|
||||||
): Unit = output(element, Canvas3DOptions.invoke(options), onSelect).render(obj)
|
|
@ -13,7 +13,7 @@ import info.laht.threekt.math.Vector3
|
|||||||
/**
|
/**
|
||||||
* An implementation of geometry builder for Three.js [BufferGeometry]
|
* An implementation of geometry builder for Three.js [BufferGeometry]
|
||||||
*/
|
*/
|
||||||
class ThreeGeometryBuilder : GeometryBuilder<BufferGeometry> {
|
public class ThreeGeometryBuilder : GeometryBuilder<BufferGeometry> {
|
||||||
|
|
||||||
private val vertices = ArrayList<Point3D>()
|
private val vertices = ArrayList<Point3D>()
|
||||||
private val faces = ArrayList<Face3>()
|
private val faces = ArrayList<Face3>()
|
||||||
|
@ -2,21 +2,28 @@ package hep.dataforge.vision.solid.three
|
|||||||
|
|
||||||
import hep.dataforge.context.*
|
import hep.dataforge.context.*
|
||||||
import hep.dataforge.meta.Meta
|
import hep.dataforge.meta.Meta
|
||||||
|
import hep.dataforge.meta.empty
|
||||||
|
import hep.dataforge.meta.invoke
|
||||||
import hep.dataforge.names.*
|
import hep.dataforge.names.*
|
||||||
import hep.dataforge.vision.Vision
|
import hep.dataforge.vision.Vision
|
||||||
|
import hep.dataforge.vision.rendering.HTMLVisionDisplay
|
||||||
import hep.dataforge.vision.solid.*
|
import hep.dataforge.vision.solid.*
|
||||||
|
import hep.dataforge.vision.solid.specifications.Canvas3DOptions
|
||||||
import hep.dataforge.vision.visible
|
import hep.dataforge.vision.visible
|
||||||
import info.laht.threekt.core.Object3D
|
import info.laht.threekt.core.Object3D
|
||||||
|
import org.w3c.dom.HTMLElement
|
||||||
import kotlin.collections.set
|
import kotlin.collections.set
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
import info.laht.threekt.objects.Group as ThreeGroup
|
import info.laht.threekt.objects.Group as ThreeGroup
|
||||||
|
|
||||||
public class ThreePlugin : AbstractPlugin() {
|
public class ThreePlugin : AbstractPlugin(), HTMLVisionDisplay<Solid, ThreeCanvas> {
|
||||||
override val tag: PluginTag get() = Companion.tag
|
override val tag: PluginTag get() = Companion.tag
|
||||||
|
|
||||||
|
public val solidManager: SolidManager by require(SolidManager)
|
||||||
|
|
||||||
private val objectFactories = HashMap<KClass<out Solid>, ThreeFactory<*>>()
|
private val objectFactories = HashMap<KClass<out Solid>, ThreeFactory<*>>()
|
||||||
private val compositeFactory = ThreeCompositeFactory(this)
|
private val compositeFactory = ThreeCompositeFactory(this)
|
||||||
private val proxyFactory = ThreeProxyFactory(this)
|
private val refFactory = ThreeReferenceFactory(this)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
//Add specialized factories here
|
//Add specialized factories here
|
||||||
@ -38,7 +45,7 @@ public class ThreePlugin : AbstractPlugin() {
|
|||||||
public fun buildObject3D(obj: Solid): Object3D {
|
public fun buildObject3D(obj: Solid): Object3D {
|
||||||
return when (obj) {
|
return when (obj) {
|
||||||
is ThreeVision -> obj.render()
|
is ThreeVision -> obj.render()
|
||||||
is Proxy -> proxyFactory(obj)
|
is SolidReference -> refFactory(obj)
|
||||||
is SolidGroup -> {
|
is SolidGroup -> {
|
||||||
val group = ThreeGroup()
|
val group = ThreeGroup()
|
||||||
obj.children.forEach { (token, child) ->
|
obj.children.forEach { (token, child) ->
|
||||||
@ -69,7 +76,7 @@ public class ThreePlugin : AbstractPlugin() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
obj.onChildrenChange(this) { nameToken, child ->
|
obj.onStructureChange(this) { nameToken, _, child ->
|
||||||
// if (name.isEmpty()) {
|
// if (name.isEmpty()) {
|
||||||
// logger.error { "Children change with empty name on $group" }
|
// logger.error { "Children change with empty name on $group" }
|
||||||
// return@onChildrenChange
|
// return@onChildrenChange
|
||||||
@ -108,6 +115,12 @@ public class ThreePlugin : AbstractPlugin() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun attachRenderer(element: HTMLElement): ThreeCanvas {
|
||||||
|
return ThreeCanvas(this, Canvas3DOptions.empty()).apply {
|
||||||
|
attach(element)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public companion object : PluginFactory<ThreePlugin> {
|
public companion object : PluginFactory<ThreePlugin> {
|
||||||
override val tag: PluginTag = PluginTag("visual.three", PluginTag.DATAFORGE_GROUP)
|
override val tag: PluginTag = PluginTag("visual.three", PluginTag.DATAFORGE_GROUP)
|
||||||
override val type: KClass<ThreePlugin> = ThreePlugin::class
|
override val type: KClass<ThreePlugin> = ThreePlugin::class
|
||||||
@ -115,6 +128,17 @@ public class ThreePlugin : AbstractPlugin() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public fun ThreePlugin.attachRenderer(
|
||||||
|
element: HTMLElement,
|
||||||
|
options: Canvas3DOptions = Canvas3DOptions.empty(),
|
||||||
|
): ThreeCanvas = ThreeCanvas(this, options).apply { attach(element) }
|
||||||
|
|
||||||
|
public fun ThreePlugin.render(
|
||||||
|
element: HTMLElement,
|
||||||
|
obj: Solid,
|
||||||
|
options: Canvas3DOptions.() -> Unit = {},
|
||||||
|
): ThreeCanvas = attachRenderer(element, Canvas3DOptions(options)).apply { render(obj) }
|
||||||
|
|
||||||
internal operator fun Object3D.set(token: NameToken, object3D: Object3D) {
|
internal operator fun Object3D.set(token: NameToken, object3D: Object3D) {
|
||||||
object3D.name = token.toString()
|
object3D.name = token.toString()
|
||||||
add(object3D)
|
add(object3D)
|
||||||
|
@ -3,18 +3,18 @@ package hep.dataforge.vision.solid.three
|
|||||||
import hep.dataforge.names.cutFirst
|
import hep.dataforge.names.cutFirst
|
||||||
import hep.dataforge.names.firstOrNull
|
import hep.dataforge.names.firstOrNull
|
||||||
import hep.dataforge.names.toName
|
import hep.dataforge.names.toName
|
||||||
import hep.dataforge.vision.solid.Proxy
|
|
||||||
import hep.dataforge.vision.solid.Proxy.Companion.PROXY_CHILD_PROPERTY_PREFIX
|
|
||||||
import hep.dataforge.vision.solid.Solid
|
import hep.dataforge.vision.solid.Solid
|
||||||
|
import hep.dataforge.vision.solid.SolidReference
|
||||||
|
import hep.dataforge.vision.solid.SolidReference.Companion.REFERENCE_CHILD_PROPERTY_PREFIX
|
||||||
import info.laht.threekt.core.BufferGeometry
|
import info.laht.threekt.core.BufferGeometry
|
||||||
import info.laht.threekt.core.Object3D
|
import info.laht.threekt.core.Object3D
|
||||||
import info.laht.threekt.objects.Mesh
|
import info.laht.threekt.objects.Mesh
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
public class ThreeProxyFactory(public val three: ThreePlugin) : ThreeFactory<Proxy> {
|
public class ThreeReferenceFactory(public val three: ThreePlugin) : ThreeFactory<SolidReference> {
|
||||||
private val cache = HashMap<Solid, Object3D>()
|
private val cache = HashMap<Solid, Object3D>()
|
||||||
|
|
||||||
override val type: KClass<Proxy> = Proxy::class
|
override val type: KClass<SolidReference> = SolidReference::class
|
||||||
|
|
||||||
private fun Object3D.replicate(): Object3D {
|
private fun Object3D.replicate(): Object3D {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
@ -30,7 +30,7 @@ public class ThreeProxyFactory(public val three: ThreePlugin) : ThreeFactory<Pro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun invoke(obj: Proxy): Object3D {
|
override fun invoke(obj: SolidReference): Object3D {
|
||||||
val template = obj.prototype
|
val template = obj.prototype
|
||||||
val cachedObject = cache.getOrPut(template) {
|
val cachedObject = cache.getOrPut(template) {
|
||||||
three.buildObject3D(template)
|
three.buildObject3D(template)
|
||||||
@ -44,12 +44,12 @@ public class ThreeProxyFactory(public val three: ThreePlugin) : ThreeFactory<Pro
|
|||||||
}
|
}
|
||||||
|
|
||||||
obj.onPropertyChange(this) { name ->
|
obj.onPropertyChange(this) { name ->
|
||||||
if (name.firstOrNull()?.body == PROXY_CHILD_PROPERTY_PREFIX) {
|
if (name.firstOrNull()?.body == REFERENCE_CHILD_PROPERTY_PREFIX) {
|
||||||
val childName = name.firstOrNull()?.index?.toName() ?: error("Wrong syntax for proxy child property: '$name'")
|
val childName = name.firstOrNull()?.index?.toName() ?: error("Wrong syntax for reference child property: '$name'")
|
||||||
val propertyName = name.cutFirst()
|
val propertyName = name.cutFirst()
|
||||||
val proxyChild = obj[childName] ?: error("Proxy child with name '$childName' not found")
|
val referenceChild = obj[childName] ?: error("Reference child with name '$childName' not found")
|
||||||
val child = object3D.findChild(childName) ?: error("Object child with name '$childName' not found")
|
val child = object3D.findChild(childName) ?: error("Object child with name '$childName' not found")
|
||||||
child.updateProperty(proxyChild, propertyName)
|
child.updateProperty(referenceChild, propertyName)
|
||||||
} else {
|
} else {
|
||||||
object3D.updateProperty(obj, name)
|
object3D.updateProperty(obj, name)
|
||||||
}
|
}
|
@ -14,15 +14,15 @@ import info.laht.threekt.objects.Mesh
|
|||||||
import info.laht.threekt.textures.Texture
|
import info.laht.threekt.textures.Texture
|
||||||
import kotlin.math.PI
|
import kotlin.math.PI
|
||||||
|
|
||||||
val Solid.euler get() = Euler(rotationX, rotationY, rotationZ, rotationOrder.name)
|
public val Solid.euler: Euler get() = Euler(rotationX, rotationY, rotationZ, rotationOrder.name)
|
||||||
|
|
||||||
val MetaItem<*>.vector get() = Vector3(node["x"].float ?: 0f, node["y"].float ?: 0f, node["z"].float ?: 0f)
|
public val MetaItem<*>.vector: Vector3 get() = Vector3(node["x"].float ?: 0f, node["y"].float ?: 0f, node["z"].float ?: 0f)
|
||||||
|
|
||||||
fun Geometry.toBufferGeometry(): BufferGeometry = BufferGeometry().apply { fromGeometry(this@toBufferGeometry) }
|
public fun Geometry.toBufferGeometry(): BufferGeometry = BufferGeometry().apply { fromGeometry(this@toBufferGeometry) }
|
||||||
|
|
||||||
internal fun Double.toRadians() = this * PI / 180
|
internal fun Double.toRadians() = this * PI / 180
|
||||||
|
|
||||||
fun CSG.toGeometry(): Geometry {
|
public fun CSG.toGeometry(): Geometry {
|
||||||
val geom = Geometry()
|
val geom = Geometry()
|
||||||
|
|
||||||
val vertices = ArrayList<Vector3>()
|
val vertices = ArrayList<Vector3>()
|
||||||
|
@ -28,7 +28,7 @@ class FX3DPlugin : AbstractPlugin() {
|
|||||||
|
|
||||||
private val objectFactories = HashMap<KClass<out Solid>, FX3DFactory<*>>()
|
private val objectFactories = HashMap<KClass<out Solid>, FX3DFactory<*>>()
|
||||||
private val compositeFactory = FXCompositeFactory(this)
|
private val compositeFactory = FXCompositeFactory(this)
|
||||||
private val proxyFactory = FXProxyFactory(this)
|
private val referenceFactory = FXReferenceFactory(this)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
//Add specialized factories here
|
//Add specialized factories here
|
||||||
@ -45,7 +45,7 @@ class FX3DPlugin : AbstractPlugin() {
|
|||||||
fun buildNode(obj: Solid): Node {
|
fun buildNode(obj: Solid): Node {
|
||||||
val binding = VisualObjectFXBinding(obj)
|
val binding = VisualObjectFXBinding(obj)
|
||||||
return when (obj) {
|
return when (obj) {
|
||||||
is Proxy -> proxyFactory(obj, binding)
|
is SolidReference -> referenceFactory(obj, binding)
|
||||||
is SolidGroup -> {
|
is SolidGroup -> {
|
||||||
Group(obj.children.mapNotNull { (token, obj) ->
|
Group(obj.children.mapNotNull { (token, obj) ->
|
||||||
(obj as? Solid)?.let {
|
(obj as? Solid)?.let {
|
||||||
|
@ -2,9 +2,8 @@ package hep.dataforge.vision.solid.fx
|
|||||||
|
|
||||||
import hep.dataforge.context.Context
|
import hep.dataforge.context.Context
|
||||||
import hep.dataforge.context.ContextAware
|
import hep.dataforge.context.ContextAware
|
||||||
import hep.dataforge.meta.Meta
|
|
||||||
import hep.dataforge.meta.empty
|
import hep.dataforge.meta.empty
|
||||||
import hep.dataforge.output.Renderer
|
import hep.dataforge.vision.Renderer
|
||||||
import hep.dataforge.vision.solid.Solid
|
import hep.dataforge.vision.solid.Solid
|
||||||
import hep.dataforge.vision.solid.specifications.Canvas3DOptions
|
import hep.dataforge.vision.solid.specifications.Canvas3DOptions
|
||||||
import javafx.application.Platform
|
import javafx.application.Platform
|
||||||
@ -81,7 +80,7 @@ class FXCanvas3D(val plugin: FX3DPlugin, val spec: Canvas3DOptions = Canvas3DOpt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun render(obj: Solid, meta: Meta) {
|
override fun render(vision: Solid) {
|
||||||
rootObject = obj
|
rootObject = vision
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,25 +2,25 @@ package hep.dataforge.vision.solid.fx
|
|||||||
|
|
||||||
import hep.dataforge.names.*
|
import hep.dataforge.names.*
|
||||||
import hep.dataforge.vision.Vision
|
import hep.dataforge.vision.Vision
|
||||||
import hep.dataforge.vision.solid.Proxy
|
import hep.dataforge.vision.solid.SolidReference
|
||||||
import javafx.scene.Group
|
import javafx.scene.Group
|
||||||
import javafx.scene.Node
|
import javafx.scene.Node
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
class FXProxyFactory(val plugin: FX3DPlugin) : FX3DFactory<Proxy> {
|
class FXReferenceFactory(val plugin: FX3DPlugin) : FX3DFactory<SolidReference> {
|
||||||
override val type: KClass<in Proxy> get() = Proxy::class
|
override val type: KClass<in SolidReference> get() = SolidReference::class
|
||||||
|
|
||||||
override fun invoke(obj: Proxy, binding: VisualObjectFXBinding): Node {
|
override fun invoke(obj: SolidReference, binding: VisualObjectFXBinding): Node {
|
||||||
val prototype = obj.prototype
|
val prototype = obj.prototype
|
||||||
val node = plugin.buildNode(prototype)
|
val node = plugin.buildNode(prototype)
|
||||||
|
|
||||||
obj.onPropertyChange(this) { name->
|
obj.onPropertyChange(this) { name->
|
||||||
if (name.firstOrNull()?.body == Proxy.PROXY_CHILD_PROPERTY_PREFIX) {
|
if (name.firstOrNull()?.body == SolidReference.REFERENCE_CHILD_PROPERTY_PREFIX) {
|
||||||
val childName = name.firstOrNull()?.index?.toName() ?: error("Wrong syntax for proxy child property: '$name'")
|
val childName = name.firstOrNull()?.index?.toName() ?: error("Wrong syntax for reference child property: '$name'")
|
||||||
val propertyName = name.cutFirst()
|
val propertyName = name.cutFirst()
|
||||||
val proxyChild = obj[childName] ?: error("Proxy child with name '$childName' not found")
|
val referenceChild = obj[childName] ?: error("Reference child with name '$childName' not found")
|
||||||
val child = node.findChild(childName) ?: error("Object child with name '$childName' not found")
|
val child = node.findChild(childName) ?: error("Object child with name '$childName' not found")
|
||||||
child.updateProperty(proxyChild, propertyName)
|
child.updateProperty(referenceChild, propertyName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return node
|
return node
|
Loading…
Reference in New Issue
Block a user