Remove dataforge-output.

This commit is contained in:
Alexander Nozik 2020-11-15 15:00:54 +03:00
parent 613624ff17
commit 6a48948c15
38 changed files with 620 additions and 284 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,5 @@
package hep.dataforge.vision
public fun interface Renderer<in V: Vision> {
public fun render(vision: V)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +0,0 @@
package hep.dataforge.vision.solid.three
fun createDataControls() {
val dat = kotlinext.js.require("dat.gui")
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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