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.
|
||||
|
||||
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
|
||||
|
||||
|
@ -6,7 +6,10 @@ plugins {
|
||||
|
||||
val dataforgeVersion by extra("0.2.0-dev-7")
|
||||
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 fxVersion by extra("14")
|
||||
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
@ -21,7 +24,6 @@ allprojects {
|
||||
|
||||
val githubProject by extra("visionforge")
|
||||
val bintrayRepo by extra("dataforge")
|
||||
val fxVersion by extra("14")
|
||||
|
||||
subprojects {
|
||||
if(name.startsWith("visionforge")) {
|
||||
|
@ -5,12 +5,11 @@ import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.meta.get
|
||||
import hep.dataforge.meta.string
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.output.OutputManager
|
||||
import hep.dataforge.output.Renderer
|
||||
import hep.dataforge.vision.Vision
|
||||
import hep.dataforge.vision.solid.three.ThreeCanvas
|
||||
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.dom.clear
|
||||
import kotlinx.html.dom.append
|
||||
@ -23,7 +22,7 @@ import kotlinx.html.span
|
||||
import org.w3c.dom.Element
|
||||
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 outputs: MutableMap<Name, ThreeCanvas> = HashMap()
|
||||
@ -46,7 +45,7 @@ class ThreeDemoGrid(element: Element, meta: Meta = Meta.EMPTY) : OutputManager {
|
||||
span("border") {
|
||||
div("col-6") {
|
||||
div { id = "output-$name" }.also {
|
||||
output = three.output(it, canvasOptions)
|
||||
output = three.attachRenderer(it, canvasOptions)
|
||||
//output.attach(it)
|
||||
}
|
||||
hr()
|
||||
|
@ -3,7 +3,6 @@ package hep.dataforge.vision.bootstrap
|
||||
import hep.dataforge.vision.react.flexColumn
|
||||
import hep.dataforge.vision.react.flexRow
|
||||
import hep.dataforge.vision.solid.SolidGroup
|
||||
import hep.dataforge.vision.solid.SolidManager
|
||||
import hep.dataforge.vision.solid.three.ThreeCanvas
|
||||
import kotlinx.css.*
|
||||
import kotlinx.css.properties.border
|
||||
@ -45,7 +44,7 @@ public external interface CanvasControlsProps : RProps {
|
||||
|
||||
public val CanvasControls: FunctionalComponent<CanvasControlsProps> = functionalComponent("CanvasControls") { props ->
|
||||
val visionManager = useMemo(
|
||||
{ props.canvas.context.plugins.fetch(SolidManager).visionManager },
|
||||
{ props.canvas.three.solidManager.visionManager },
|
||||
arrayOf(props.canvas)
|
||||
)
|
||||
flexColumn {
|
||||
|
@ -7,7 +7,6 @@ import hep.dataforge.vision.solid.Solid
|
||||
import hep.dataforge.vision.solid.specifications.Canvas3DOptions
|
||||
import hep.dataforge.vision.solid.three.ThreeCanvas
|
||||
import hep.dataforge.vision.solid.three.ThreePlugin
|
||||
import hep.dataforge.vision.solid.three.output
|
||||
import kotlinx.css.*
|
||||
import org.w3c.dom.Element
|
||||
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 three: ThreePlugin = props.context.plugins.fetch(ThreePlugin)
|
||||
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)
|
||||
canvas = newCanvas
|
||||
}
|
||||
|
@ -4,13 +4,15 @@ plugins {
|
||||
|
||||
val dataforgeVersion: String by rootProject.extra
|
||||
val kotlinWrappersVersion: String by rootProject.extra
|
||||
val htmlVersion: String by rootProject.extra
|
||||
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api("hep.dataforge:dataforge-output:$dataforgeVersion")
|
||||
api("hep.dataforge:dataforge-context:$dataforgeVersion")
|
||||
api("org.jetbrains.kotlinx:kotlinx-html:$htmlVersion")
|
||||
}
|
||||
}
|
||||
jvmMain {
|
||||
@ -27,7 +29,6 @@ kotlin {
|
||||
}
|
||||
jsMain {
|
||||
dependencies {
|
||||
api("hep.dataforge:dataforge-output-html:$dataforgeVersion")
|
||||
api("org.jetbrains:kotlin-extensions:1.0.1-$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?) {
|
||||
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 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
|
||||
*/
|
||||
public fun removeChildrenChangeListener(owner: Any?)
|
||||
public fun removeStructureChangeListener(owner: Any?)
|
||||
|
||||
// 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
|
||||
private val structureChangeListeners = HashSet<StructureChangeListener>()
|
||||
@ -53,7 +56,7 @@ public open class VisionGroupBase : VisionBase(), MutableVisionGroup {
|
||||
/**
|
||||
* 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(
|
||||
StructureChangeListener(
|
||||
owner,
|
||||
@ -65,29 +68,31 @@ public open class VisionGroupBase : VisionBase(), MutableVisionGroup {
|
||||
/**
|
||||
* Remove children change listener
|
||||
*/
|
||||
override fun removeChildrenChangeListener(owner: Any?) {
|
||||
structureChangeListeners.removeAll { it.owner === owner }
|
||||
override fun removeStructureChangeListener(owner: Any?) {
|
||||
structureChangeListeners.removeAll { owner == null || it.owner === owner }
|
||||
}
|
||||
|
||||
/**
|
||||
* Propagate children change event upwards
|
||||
*/
|
||||
protected fun childrenChanged(name: NameToken, child: Vision?) {
|
||||
structureChangeListeners.forEach { it.callback(name, child) }
|
||||
protected fun childrenChanged(name: NameToken, before: Vision?, after: Vision?) {
|
||||
structureChangeListeners.forEach { it.callback(name, before, after) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a child with given name token
|
||||
*/
|
||||
public fun removeChild(token: NameToken) {
|
||||
childrenInternal.remove(token)
|
||||
public fun removeChild(token: NameToken): Vision? {
|
||||
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.
|
||||
*/
|
||||
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()
|
||||
@ -95,7 +100,7 @@ public open class VisionGroupBase : VisionBase(), MutableVisionGroup {
|
||||
/**
|
||||
* 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) {
|
||||
child.parent = this
|
||||
childrenInternal[token] = child
|
||||
@ -114,7 +119,7 @@ public open class VisionGroupBase : VisionBase(), MutableVisionGroup {
|
||||
val token = name.tokens.first()
|
||||
when (val current = children[token]) {
|
||||
null -> createGroup().also { child ->
|
||||
attach(token, child)
|
||||
attachChild(token, child)
|
||||
}
|
||||
is VisionGroupBase -> current
|
||||
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 -> {
|
||||
val token = name.tokens.first()
|
||||
val before = children[token]
|
||||
if (child == null) {
|
||||
removeChild(token)
|
||||
} else {
|
||||
attach(token, child)
|
||||
attachChild(token, child)
|
||||
}
|
||||
childrenChanged(token, child)
|
||||
childrenChanged(token, before, child)
|
||||
}
|
||||
else -> {
|
||||
//TODO add safety check
|
||||
@ -165,7 +171,7 @@ public open class VisionGroupBase : VisionBase(), MutableVisionGroup {
|
||||
when {
|
||||
child is NullVision -> removeChild(token)
|
||||
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
|
||||
?: 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> {
|
||||
override val tag: PluginTag = PluginTag(name = "vision", group = PluginTag.DATAFORGE_GROUP)
|
||||
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)
|
||||
*/
|
||||
interface ValueChooser {
|
||||
public interface ValueChooser {
|
||||
|
||||
/**
|
||||
* 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
|
||||
if (proto[templateName] == null) {
|
||||
solids.addSolid(root, solid, name)
|
||||
@ -66,7 +66,7 @@ private class GDMLTransformer(val settings: GDMLTransformerSettings) {
|
||||
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()
|
||||
if (proto[templateName] == null) {
|
||||
proto[templateName] = volume(root, volume)
|
||||
|
@ -1,14 +1,12 @@
|
||||
package hep.dataforge.vision.gdml
|
||||
|
||||
import hep.dataforge.vision.solid.AbstractProxy
|
||||
import hep.dataforge.vision.solid.prototype
|
||||
import hep.dataforge.vision.visitor.countDistinct
|
||||
import hep.dataforge.vision.visitor.countDistinctBy
|
||||
import hep.dataforge.vision.visitor.flowStatistics
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import nl.adaptivity.xmlutil.StAXReader
|
||||
import kscience.gdml.GDML
|
||||
import nl.adaptivity.xmlutil.StAXReader
|
||||
import java.io.File
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
|
@ -6,3 +6,10 @@ plugins {
|
||||
|
||||
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 {
|
||||
dependencies {
|
||||
implementation(npm("three", "0.114.0"))
|
||||
implementation(npm("three", "0.122.0"))
|
||||
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.asName
|
||||
import hep.dataforge.names.plus
|
||||
import hep.dataforge.output.Renderer
|
||||
import hep.dataforge.values.ValueType
|
||||
import hep.dataforge.values.asValue
|
||||
import hep.dataforge.vision.Renderer
|
||||
import hep.dataforge.vision.Vision
|
||||
import hep.dataforge.vision.Vision.Companion.VISIBLE_KEY
|
||||
import hep.dataforge.vision.enum
|
||||
@ -73,11 +73,11 @@ public interface Solid : Vision {
|
||||
|
||||
item(SolidMaterial.MATERIAL_KEY.toString(), SolidMaterial.descriptor)
|
||||
|
||||
enum(ROTATION_ORDER_KEY,default = RotationOrder.XYZ)
|
||||
enum(ROTATION_ORDER_KEY, default = RotationOrder.XYZ)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun solidEquals(first: Solid, second: Solid): Boolean{
|
||||
internal fun solidEquals(first: Solid, second: Solid): Boolean {
|
||||
if (first.position != second.position) return false
|
||||
if (first.rotation != second.rotation) return false
|
||||
if (first.scale != second.scale) return false
|
||||
@ -85,8 +85,8 @@ public interface Solid : Vision {
|
||||
return true
|
||||
}
|
||||
|
||||
internal fun solidHashCode(solid: Solid): Int{
|
||||
var result = + (solid.position?.hashCode() ?: 0)
|
||||
internal fun solidHashCode(solid: Solid): Int {
|
||||
var result = +(solid.position?.hashCode() ?: 0)
|
||||
result = 31 * result + (solid.rotation?.hashCode() ?: 0)
|
||||
result = 31 * result + (solid.scale?.hashCode() ?: 0)
|
||||
result = 31 * result + (solid.properties?.hashCode() ?: 0)
|
||||
@ -104,8 +104,7 @@ public var Solid.layer: Int
|
||||
config[LAYER_KEY] = value.asValue()
|
||||
}
|
||||
|
||||
public fun Renderer<Solid>.render(meta: Meta = Meta.EMPTY, action: SolidGroup.() -> Unit): Unit =
|
||||
render(SolidGroup().apply(action), meta)
|
||||
public fun Renderer<Solid>.render(action: SolidGroup.() -> Unit): Unit = render(SolidGroup().apply(action))
|
||||
|
||||
// Common properties
|
||||
|
||||
|
@ -35,7 +35,7 @@ public class SolidManager(meta: Meta) : AbstractPlugin(meta) {
|
||||
|
||||
private fun PolymorphicModuleBuilder<Solid>.solids() {
|
||||
subclass(SolidGroup.serializer())
|
||||
subclass(Proxy.serializer())
|
||||
subclass(SolidReference.serializer())
|
||||
subclass(Composite.serializer())
|
||||
subclass(Tube.serializer())
|
||||
subclass(Box.serializer())
|
||||
|
@ -9,7 +9,7 @@ import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import kotlin.collections.set
|
||||
|
||||
public abstract class AbstractProxy : BasicSolid(), VisionGroup {
|
||||
public abstract class AbstractReference : BasicSolid(), VisionGroup {
|
||||
public abstract val prototype: Solid
|
||||
|
||||
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
|
||||
@SerialName("solid.proxy")
|
||||
public class Proxy(
|
||||
@SerialName("solid.ref")
|
||||
public class SolidReference(
|
||||
public val templateName: Name,
|
||||
) : AbstractProxy(), Solid {
|
||||
) : AbstractReference(), Solid {
|
||||
|
||||
/**
|
||||
* Recursively search for defined template in the parent
|
||||
@ -67,15 +67,15 @@ public class Proxy(
|
||||
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
|
||||
?.filter { !it.key.toString().startsWith("@") }
|
||||
?.mapValues {
|
||||
ProxyChild(it.key.asName())
|
||||
ReferenceChild(it.key.asName())
|
||||
} ?: emptyMap()
|
||||
|
||||
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 {
|
||||
@ -89,17 +89,17 @@ public class Proxy(
|
||||
* 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).
|
||||
*/
|
||||
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 styleSheet: StyleSheet get() = this@Proxy.styleSheet
|
||||
override val styleSheet: StyleSheet get() = this@SolidReference.styleSheet
|
||||
|
||||
override val children: Map<NameToken, Vision>
|
||||
get() = (prototype as? VisionGroup)?.children
|
||||
?.filter { !it.key.toString().startsWith("@") }
|
||||
?.mapValues { (key, _) ->
|
||||
ProxyChild(name + key.asName())
|
||||
ReferenceChild(name + key.asName())
|
||||
} ?: emptyMap()
|
||||
|
||||
override var properties: Config?
|
||||
@ -108,12 +108,12 @@ public class Proxy(
|
||||
if (value == null) {
|
||||
propertyCache.remove(name)?.also {
|
||||
//Removing listener if it is present
|
||||
removeChangeListener(this@Proxy)
|
||||
removeChangeListener(this@SolidReference)
|
||||
}
|
||||
} else {
|
||||
propertyCache[name] = value.also {
|
||||
onPropertyChange(this@Proxy) { propertyName ->
|
||||
this@Proxy.propertyChanged(childPropertyName(name, propertyName))
|
||||
onPropertyChange(this@SolidReference) { propertyName ->
|
||||
this@SolidReference.propertyChanged(childPropertyName(name, propertyName))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -122,16 +122,16 @@ public class Proxy(
|
||||
}
|
||||
|
||||
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
|
||||
get() = when (this) {
|
||||
is AbstractProxy -> prototype
|
||||
is AbstractReference -> prototype
|
||||
else -> this
|
||||
}
|
||||
|
||||
@ -141,16 +141,16 @@ public val Vision.prototype: Vision
|
||||
public fun SolidGroup.ref(
|
||||
templateName: Name,
|
||||
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,
|
||||
obj: Solid,
|
||||
templateName: Name = name.toName(),
|
||||
): Proxy {
|
||||
): SolidReference {
|
||||
val existing = getPrototype(templateName)
|
||||
if (existing == null) {
|
||||
prototypes {
|
||||
@ -159,5 +159,5 @@ public fun SolidGroup.proxy(
|
||||
} else if (existing != obj) {
|
||||
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
|
||||
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.names.Name
|
||||
|
||||
public class Canvas3DOptions : Scheme() {
|
||||
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 maxHeight: Number by number { maxSize }
|
||||
|
||||
public var onSelect: ((Name?)->Unit)? = null
|
||||
|
||||
|
||||
public companion object : SchemeSpec<Canvas3DOptions>(::Canvas3DOptions)
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ internal object RemoveSingleChild : VisualTreeTransform<SolidGroup>() {
|
||||
override fun SolidGroup.transformInPlace() {
|
||||
fun MutableVisionGroup.replaceChildren() {
|
||||
children.forEach { (childName, parent) ->
|
||||
if (parent is Proxy) return@forEach //ignore refs
|
||||
if (parent is SolidReference) return@forEach //ignore refs
|
||||
if (parent is MutableVisionGroup) {
|
||||
parent.replaceChildren()
|
||||
}
|
||||
|
@ -5,8 +5,8 @@ import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.vision.MutableVisionGroup
|
||||
import hep.dataforge.vision.VisionGroup
|
||||
import hep.dataforge.vision.solid.Proxy
|
||||
import hep.dataforge.vision.solid.SolidGroup
|
||||
import hep.dataforge.vision.solid.SolidReference
|
||||
|
||||
@DFExperimental
|
||||
internal object UnRef : VisualTreeTransform<SolidGroup>() {
|
||||
@ -17,7 +17,7 @@ internal object UnRef : VisualTreeTransform<SolidGroup>() {
|
||||
counter.forEach { (key, 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
|
||||
}
|
||||
|
||||
@ -27,9 +27,9 @@ internal object UnRef : VisualTreeTransform<SolidGroup>() {
|
||||
|
||||
private fun MutableVisionGroup.unref(name: Name) {
|
||||
(this as? SolidGroup)?.prototypes?.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)
|
||||
children.filter { (it.value as? SolidReference)?.templateName == name }.forEach { (key, value) ->
|
||||
val reference = value as SolidReference
|
||||
val newChild = mergeChild(reference, reference.prototype)
|
||||
newChild.parent = null
|
||||
set(key.asName(), newChild) // replace proxy with merged object
|
||||
}
|
||||
|
@ -58,8 +58,8 @@ class PropertyTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testProxyStyleProperty() {
|
||||
var box: Proxy? = null
|
||||
fun testReferenceStyleProperty() {
|
||||
var box: SolidReference? = null
|
||||
val group = SolidGroup{
|
||||
styleSheet {
|
||||
set("testStyle") {
|
||||
|
@ -3,7 +3,6 @@ package hep.dataforge.vision.solid
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.toName
|
||||
import hep.dataforge.vision.MutableVisionGroup
|
||||
import hep.dataforge.vision.Vision
|
||||
import hep.dataforge.vision.get
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
@ -12,13 +11,13 @@ import kotlin.test.assertEquals
|
||||
/**
|
||||
* Create and attach new proxied group
|
||||
*/
|
||||
fun SolidGroup.proxyGroup(
|
||||
fun SolidGroup.refGroup(
|
||||
name: String,
|
||||
templateName: Name = name.toName(),
|
||||
block: MutableVisionGroup.() -> Unit
|
||||
): Proxy {
|
||||
): SolidReference {
|
||||
val group = SolidGroup().apply(block)
|
||||
return proxy(name, group, templateName)
|
||||
return ref(name, group, templateName)
|
||||
}
|
||||
|
||||
|
||||
@ -44,8 +43,8 @@ class SerializationTest {
|
||||
z = -100
|
||||
}
|
||||
val group = SolidGroup{
|
||||
proxy("cube", cube)
|
||||
proxyGroup("pg", "pg.content".toName()){
|
||||
ref("cube", cube)
|
||||
refGroup("pg", "pg.content".toName()){
|
||||
sphere(50){
|
||||
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
|
||||
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.meta.getItem
|
||||
import hep.dataforge.meta.string
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.plus
|
||||
import hep.dataforge.names.toName
|
||||
import hep.dataforge.output.Renderer
|
||||
import hep.dataforge.vision.Colors
|
||||
import hep.dataforge.vision.Renderer
|
||||
import hep.dataforge.vision.solid.Solid
|
||||
import hep.dataforge.vision.solid.specifications.*
|
||||
import hep.dataforge.vision.solid.three.ThreeMaterials.HIGHLIGHT_MATERIAL
|
||||
@ -41,28 +41,24 @@ import kotlin.math.sin
|
||||
public class ThreeCanvas(
|
||||
public val three: ThreePlugin,
|
||||
public val options: Canvas3DOptions,
|
||||
public val onClick: ((Name?) -> Unit)? = null,
|
||||
) : Renderer<Solid> {
|
||||
|
||||
override val context: Context get() = three.context
|
||||
|
||||
public var content: Solid? = null
|
||||
private set
|
||||
|
||||
private var root: Object3D? = null
|
||||
|
||||
private val raycaster = Raycaster()
|
||||
private val mousePosition: Vector2 = Vector2()
|
||||
|
||||
public val axes: AxesHelper = AxesHelper(options.axes.size.toInt()).apply {
|
||||
visible = options.axes.visible
|
||||
}
|
||||
public var content: Solid? = null
|
||||
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)
|
||||
}
|
||||
|
||||
public val camera: PerspectiveCamera = buildCamera(options.camera)
|
||||
public var camera: PerspectiveCamera = buildCamera(options.camera)
|
||||
private set
|
||||
|
||||
private var picked: Object3D? = null
|
||||
|
||||
@ -97,7 +93,7 @@ public class ThreeCanvas(
|
||||
|
||||
element.addEventListener("mousedown", {
|
||||
val picked = pick()
|
||||
onClick?.invoke(picked?.fullName())
|
||||
options.onSelect?.invoke(picked?.fullName())
|
||||
}, false)
|
||||
|
||||
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()
|
||||
|
||||
|
||||
val object3D = three.buildObject3D(obj)
|
||||
val object3D = three.buildObject3D(vision)
|
||||
object3D.name = "@root"
|
||||
scene.add(object3D)
|
||||
content = obj
|
||||
content = vision
|
||||
root = object3D
|
||||
}
|
||||
|
||||
@ -261,16 +256,3 @@ public class ThreeCanvas(
|
||||
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]
|
||||
*/
|
||||
class ThreeGeometryBuilder : GeometryBuilder<BufferGeometry> {
|
||||
public class ThreeGeometryBuilder : GeometryBuilder<BufferGeometry> {
|
||||
|
||||
private val vertices = ArrayList<Point3D>()
|
||||
private val faces = ArrayList<Face3>()
|
||||
|
@ -2,21 +2,28 @@ package hep.dataforge.vision.solid.three
|
||||
|
||||
import hep.dataforge.context.*
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.meta.empty
|
||||
import hep.dataforge.meta.invoke
|
||||
import hep.dataforge.names.*
|
||||
import hep.dataforge.vision.Vision
|
||||
import hep.dataforge.vision.rendering.HTMLVisionDisplay
|
||||
import hep.dataforge.vision.solid.*
|
||||
import hep.dataforge.vision.solid.specifications.Canvas3DOptions
|
||||
import hep.dataforge.vision.visible
|
||||
import info.laht.threekt.core.Object3D
|
||||
import org.w3c.dom.HTMLElement
|
||||
import kotlin.collections.set
|
||||
import kotlin.reflect.KClass
|
||||
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
|
||||
|
||||
public val solidManager: SolidManager by require(SolidManager)
|
||||
|
||||
private val objectFactories = HashMap<KClass<out Solid>, ThreeFactory<*>>()
|
||||
private val compositeFactory = ThreeCompositeFactory(this)
|
||||
private val proxyFactory = ThreeProxyFactory(this)
|
||||
private val refFactory = ThreeReferenceFactory(this)
|
||||
|
||||
init {
|
||||
//Add specialized factories here
|
||||
@ -38,7 +45,7 @@ public class ThreePlugin : AbstractPlugin() {
|
||||
public fun buildObject3D(obj: Solid): Object3D {
|
||||
return when (obj) {
|
||||
is ThreeVision -> obj.render()
|
||||
is Proxy -> proxyFactory(obj)
|
||||
is SolidReference -> refFactory(obj)
|
||||
is SolidGroup -> {
|
||||
val group = ThreeGroup()
|
||||
obj.children.forEach { (token, child) ->
|
||||
@ -56,7 +63,7 @@ public class ThreePlugin : AbstractPlugin() {
|
||||
updatePosition(obj)
|
||||
//obj.onChildrenChange()
|
||||
|
||||
obj.onPropertyChange(this) { name->
|
||||
obj.onPropertyChange(this) { name ->
|
||||
if (
|
||||
name.startsWith(Solid.POSITION_KEY) ||
|
||||
name.startsWith(Solid.ROTATION) ||
|
||||
@ -69,7 +76,7 @@ public class ThreePlugin : AbstractPlugin() {
|
||||
}
|
||||
}
|
||||
|
||||
obj.onChildrenChange(this) { nameToken, child ->
|
||||
obj.onStructureChange(this) { nameToken, _, child ->
|
||||
// if (name.isEmpty()) {
|
||||
// logger.error { "Children change with empty name on $group" }
|
||||
// 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> {
|
||||
override val tag: PluginTag = PluginTag("visual.three", PluginTag.DATAFORGE_GROUP)
|
||||
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) {
|
||||
object3D.name = token.toString()
|
||||
add(object3D)
|
||||
|
@ -3,18 +3,18 @@ package hep.dataforge.vision.solid.three
|
||||
import hep.dataforge.names.cutFirst
|
||||
import hep.dataforge.names.firstOrNull
|
||||
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.SolidReference
|
||||
import hep.dataforge.vision.solid.SolidReference.Companion.REFERENCE_CHILD_PROPERTY_PREFIX
|
||||
import info.laht.threekt.core.BufferGeometry
|
||||
import info.laht.threekt.core.Object3D
|
||||
import info.laht.threekt.objects.Mesh
|
||||
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>()
|
||||
|
||||
override val type: KClass<Proxy> = Proxy::class
|
||||
override val type: KClass<SolidReference> = SolidReference::class
|
||||
|
||||
private fun Object3D.replicate(): Object3D {
|
||||
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 cachedObject = cache.getOrPut(template) {
|
||||
three.buildObject3D(template)
|
||||
@ -44,12 +44,12 @@ public class ThreeProxyFactory(public val three: ThreePlugin) : ThreeFactory<Pro
|
||||
}
|
||||
|
||||
obj.onPropertyChange(this) { name ->
|
||||
if (name.firstOrNull()?.body == PROXY_CHILD_PROPERTY_PREFIX) {
|
||||
val childName = name.firstOrNull()?.index?.toName() ?: error("Wrong syntax for proxy child property: '$name'")
|
||||
if (name.firstOrNull()?.body == REFERENCE_CHILD_PROPERTY_PREFIX) {
|
||||
val childName = name.firstOrNull()?.index?.toName() ?: error("Wrong syntax for reference child property: '$name'")
|
||||
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")
|
||||
child.updateProperty(proxyChild, propertyName)
|
||||
child.updateProperty(referenceChild, propertyName)
|
||||
} else {
|
||||
object3D.updateProperty(obj, name)
|
||||
}
|
@ -14,15 +14,15 @@ import info.laht.threekt.objects.Mesh
|
||||
import info.laht.threekt.textures.Texture
|
||||
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
|
||||
|
||||
fun CSG.toGeometry(): Geometry {
|
||||
public fun CSG.toGeometry(): Geometry {
|
||||
val geom = Geometry()
|
||||
|
||||
val vertices = ArrayList<Vector3>()
|
||||
|
@ -28,7 +28,7 @@ class FX3DPlugin : AbstractPlugin() {
|
||||
|
||||
private val objectFactories = HashMap<KClass<out Solid>, FX3DFactory<*>>()
|
||||
private val compositeFactory = FXCompositeFactory(this)
|
||||
private val proxyFactory = FXProxyFactory(this)
|
||||
private val referenceFactory = FXReferenceFactory(this)
|
||||
|
||||
init {
|
||||
//Add specialized factories here
|
||||
@ -45,7 +45,7 @@ class FX3DPlugin : AbstractPlugin() {
|
||||
fun buildNode(obj: Solid): Node {
|
||||
val binding = VisualObjectFXBinding(obj)
|
||||
return when (obj) {
|
||||
is Proxy -> proxyFactory(obj, binding)
|
||||
is SolidReference -> referenceFactory(obj, binding)
|
||||
is SolidGroup -> {
|
||||
Group(obj.children.mapNotNull { (token, obj) ->
|
||||
(obj as? Solid)?.let {
|
||||
|
@ -2,9 +2,8 @@ package hep.dataforge.vision.solid.fx
|
||||
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.context.ContextAware
|
||||
import hep.dataforge.meta.Meta
|
||||
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.specifications.Canvas3DOptions
|
||||
import javafx.application.Platform
|
||||
@ -81,7 +80,7 @@ class FXCanvas3D(val plugin: FX3DPlugin, val spec: Canvas3DOptions = Canvas3DOpt
|
||||
}
|
||||
}
|
||||
|
||||
override fun render(obj: Solid, meta: Meta) {
|
||||
rootObject = obj
|
||||
override fun render(vision: Solid) {
|
||||
rootObject = vision
|
||||
}
|
||||
}
|
@ -2,25 +2,25 @@ package hep.dataforge.vision.solid.fx
|
||||
|
||||
import hep.dataforge.names.*
|
||||
import hep.dataforge.vision.Vision
|
||||
import hep.dataforge.vision.solid.Proxy
|
||||
import hep.dataforge.vision.solid.SolidReference
|
||||
import javafx.scene.Group
|
||||
import javafx.scene.Node
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
class FXProxyFactory(val plugin: FX3DPlugin) : FX3DFactory<Proxy> {
|
||||
override val type: KClass<in Proxy> get() = Proxy::class
|
||||
class FXReferenceFactory(val plugin: FX3DPlugin) : FX3DFactory<SolidReference> {
|
||||
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 node = plugin.buildNode(prototype)
|
||||
|
||||
obj.onPropertyChange(this) { name->
|
||||
if (name.firstOrNull()?.body == Proxy.PROXY_CHILD_PROPERTY_PREFIX) {
|
||||
val childName = name.firstOrNull()?.index?.toName() ?: error("Wrong syntax for proxy child property: '$name'")
|
||||
if (name.firstOrNull()?.body == SolidReference.REFERENCE_CHILD_PROPERTY_PREFIX) {
|
||||
val childName = name.firstOrNull()?.index?.toName() ?: error("Wrong syntax for reference child property: '$name'")
|
||||
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")
|
||||
child.updateProperty(proxyChild, propertyName)
|
||||
child.updateProperty(referenceChild, propertyName)
|
||||
}
|
||||
}
|
||||
return node
|
Loading…
Reference in New Issue
Block a user