Working Server server provider.

This commit is contained in:
Alexander Nozik 2020-12-06 19:16:48 +03:00
parent 9b42d4f186
commit a85cd828e6
25 changed files with 409 additions and 177 deletions

View File

@ -1,20 +1,51 @@
plugins { plugins {
id("ru.mipt.npm.mpp") id("ru.mipt.npm.mpp")
application
} }
val kvisionVersion: String = "3.16.2"
group = "ru.mipt.npm"
//val kvisionVersion: String = "3.16.2"
kscience{ kscience{
useSerialization{
json()
}
application() application()
} }
val ktorVersion: String by rootProject.extra
kotlin { kotlin {
afterEvaluate {
val jsBrowserDistribution by tasks.getting
jvm {
withJava()
compilations[org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.MAIN_COMPILATION_NAME]?.apply {
tasks.getByName<ProcessResources>(processResourcesTaskName) {
dependsOn(jsBrowserDistribution)
afterEvaluate {
from(jsBrowserDistribution)
}
}
}
}
}
sourceSets { sourceSets {
commonMain { commonMain {
dependencies { dependencies {
implementation(project(":visionforge-solid")) implementation(project(":visionforge-solid"))
} }
} }
jvmMain {
dependencies {
implementation(project(":visionforge-server"))
}
}
jsMain { jsMain {
dependencies { dependencies {
implementation(project(":visionforge-threejs")) implementation(project(":visionforge-threejs"))
@ -22,3 +53,7 @@ kotlin {
} }
} }
} }
application {
mainClass.set("ru.mipt.npm.sat.SatServerKt")
}

View File

@ -1,34 +1,40 @@
package ru.mipt.npm.sat package ru.mipt.npm.sat
import hep.dataforge.Application
import hep.dataforge.context.Global import hep.dataforge.context.Global
import hep.dataforge.meta.invoke import hep.dataforge.vision.client.VisionClient
import hep.dataforge.startApplication import hep.dataforge.vision.client.fetchAndRenderAllVisions
import hep.dataforge.vision.solid.three.ThreePlugin import hep.dataforge.vision.solid.three.ThreePlugin
import hep.dataforge.vision.solid.three.render import kotlinx.browser.window
import kotlinx.browser.document
import org.w3c.dom.HTMLElement
private class SatDemoApp : Application { //private class SatDemoApp : Application {
//
override fun start(state: Map<String, Any>) { // override fun start(state: Map<String, Any>) {
val element = document.getElementById("canvas") as? HTMLElement // val element = document.getElementById("canvas") as? HTMLElement
?: error("Element with id 'canvas' not found on page") // ?: error("Element with id 'canvas' not found on page")
val three = Global.plugins.fetch(ThreePlugin) // val three = Global.plugins.fetch(ThreePlugin)
val sat = visionOfSatellite( //
ySegments = 3, // val sat = visionOfSatellite(
) // ySegments = 3,
three.render(element, sat){ // )
minSize = 500 // three.render(element, sat){
axes{ // minSize = 500
size = 500.0 // axes{
visible = true // size = 500.0
} // visible = true
} // }
} // }
// }
} //
//}
//
//fun main() {
// startApplication(::SatDemoApp)
//}
fun main() { fun main() {
startApplication(::SatDemoApp) //Loading three-js renderer
Global.plugins.load(ThreePlugin)
window.onload = {
Global.plugins.fetch(VisionClient).fetchAndRenderAllVisions()
}
} }

View File

@ -0,0 +1,35 @@
package ru.mipt.npm.sat
import hep.dataforge.context.Global
import hep.dataforge.names.asName
import hep.dataforge.vision.server.visionModule
import hep.dataforge.vision.solid.SolidManager
import io.ktor.server.cio.CIO
import io.ktor.server.engine.embeddedServer
import io.ktor.util.KtorExperimentalAPI
import kotlinx.html.script
@OptIn(KtorExperimentalAPI::class)
fun main() {
val sat = visionOfSatellite(
ySegments = 3,
)
val context = Global.context("SAT"){
plugin(SolidManager)
}
embeddedServer(CIO, 8080, host = "localhost"){
visionModule(context).apply {
header {
script {
src = "sat-demo.js"
}
}
page {
vision("main".asName(), sat)
}
}
}.start(wait = true)
}

View File

@ -12,16 +12,30 @@ repositories{
} }
kotlin { kotlin {
jvm()
js(IR) { js(IR) {
browser {} browser {
}
binaries.executable()
} }
sourceSets { sourceSets {
commonMain { commonMain {
dependencies { dependencies {
api(project(":visionforge-solid")) implementation(project(":visionforge-solid"))
api(project(":visionforge-gdml")) implementation(project(":visionforge-gdml"))
api(project(":ui:bootstrap"))
}
}
val jsMain by getting{
dependencies {
implementation(project(":ui:bootstrap"))
}
}
val jvmMain by getting{
dependencies {
implementation("com.github.Ricky12Awesome:json-schema-serialization:0.6.6")
} }
} }
} }

View File

@ -1,4 +1,22 @@
//package hep.dataforge.vision.solid package hep.dataforge.vision.solid
import com.github.ricky12awesome.jss.encodeToSchema
import kotlinx.serialization.json.Json
fun main() {
val schema = Json {
serializersModule = SolidManager.serializersModuleForSolids
prettyPrintIndent = " "
prettyPrint = true
ignoreUnknownKeys = true
isLenient = true
coerceInputValues = true
encodeDefaults = true
}.encodeToSchema(SolidGroup.serializer(), generateDefinitions = false)
println(schema)
}
// //
//import hep.dataforge.meta.JSON_PRETTY //import hep.dataforge.meta.JSON_PRETTY
//import kotlinx.serialization.* //import kotlinx.serialization.*

View File

@ -54,6 +54,7 @@ public class VisionManager(meta: Meta) : AbstractPlugin(meta) {
private val defaultSerialModule: SerializersModule = SerializersModule { private val defaultSerialModule: SerializersModule = SerializersModule {
polymorphic(Vision::class) { polymorphic(Vision::class) {
default { VisionBase.serializer() }
subclass(VisionBase.serializer()) subclass(VisionBase.serializer())
subclass(VisionGroupBase.serializer()) subclass(VisionGroupBase.serializer())
} }

View File

@ -63,5 +63,7 @@ public abstract class HtmlOutputScope<R, V : Vision>(
public companion object { public companion object {
public const val OUTPUT_CLASS: String = "visionforge-output" public const val OUTPUT_CLASS: String = "visionforge-output"
public const val OUTPUT_NAME_ATTRIBUTE: String = "data-output-name" public const val OUTPUT_NAME_ATTRIBUTE: String = "data-output-name"
public const val OUTPUT_ENDPOINT_ATTRIBUTE: String = "data-output-endpoint"
public const val DEFAULT_ENDPOINT: String = "."
} }
} }

View File

@ -1,43 +0,0 @@
package hep.dataforge.vision.client
import hep.dataforge.context.Context
import hep.dataforge.context.Global
import hep.dataforge.vision.VisionManager
import hep.dataforge.vision.html.HtmlOutputScope
import kotlinx.browser.window
import org.w3c.dom.Element
import org.w3c.dom.get
import org.w3c.dom.url.URL
@JsExport
public class ClientVisionManager {
private val visionForgeContext: Context = Global.context("client") {
plugin(VisionManager)
}
private val visionManager: VisionManager = visionForgeContext.plugins.fetch(VisionManager)
/**
* Up-going tree traversal in search for endpoint attribute
*/
private fun resolveEndpoint(element: Element?): String {
if(element == null) return DEFAULT_ENDPOINT
val attribute = element.attributes[OUTPUT_ENDPOINT_ATTRIBUTE]
return attribute?.value ?: resolveEndpoint(element.parentElement)
}
public fun renderVision(element: Element){
if(!element.classList.contains(HtmlOutputScope.OUTPUT_CLASS)) error("The element $element is not an output element")
val endpoint = URL(resolveEndpoint(element))
window.fetch("$endpoint/vision").then {response->
TODO()
}
}
public companion object {
public const val OUTPUT_ENDPOINT_ATTRIBUTE: String = "data-output-endpoint"
public const val DEFAULT_ENDPOINT: String = ".."
}
}

View File

@ -0,0 +1,124 @@
package hep.dataforge.vision.client
import hep.dataforge.context.*
import hep.dataforge.meta.Meta
import hep.dataforge.vision.Vision
import hep.dataforge.vision.VisionManager
import hep.dataforge.vision.html.HtmlOutputScope
import hep.dataforge.vision.html.HtmlOutputScope.Companion.OUTPUT_ENDPOINT_ATTRIBUTE
import hep.dataforge.vision.html.HtmlOutputScope.Companion.OUTPUT_NAME_ATTRIBUTE
import kotlinx.browser.document
import kotlinx.browser.window
import org.w3c.dom.Element
import org.w3c.dom.WebSocket
import org.w3c.dom.asList
import org.w3c.dom.get
import org.w3c.dom.url.URL
import kotlin.reflect.KClass
public class VisionClient : AbstractPlugin() {
override val tag: PluginTag get() = Companion.tag
private val visionManager: VisionManager by require(VisionManager)
/**
* Up-going tree traversal in search for endpoint attribute
*/
private fun resolveEndpoint(element: Element?): String {
if (element == null) return window.location.href
val attribute = element.attributes[OUTPUT_ENDPOINT_ATTRIBUTE]
return attribute?.value ?: resolveEndpoint(element.parentElement)
}
private fun resolveName(element: Element): String? {
val attribute = element.attributes[OUTPUT_NAME_ATTRIBUTE]
return attribute?.value
}
private fun getRenderers() = context.gather<ElementVisionRenderer>(ElementVisionRenderer.TYPE).values
public fun findRendererFor(vision: Vision): ElementVisionRenderer? = getRenderers().maxByOrNull { it.rateVision(vision) }
/**
* Fetch from server and render a vision, described in a given with [HtmlOutputScope.OUTPUT_CLASS] class.
*/
public fun fetchAndRenderVision(element: Element, requestUpdates: Boolean = true) {
val name = resolveName(element) ?: error("The element is not a vision output")
console.info("Found DF output with name $name")
if (!element.classList.contains(HtmlOutputScope.OUTPUT_CLASS)) error("The element $element is not an output element")
val endpoint = resolveEndpoint(element)
console.info("Vision server is resolved to $endpoint")
val fetchUrl = URL(endpoint).apply {
searchParams.append("name", name)
pathname += "/vision"
}
window.fetch(fetchUrl).then { response ->
if (response.ok) {
response.text().then { text ->
val vision = visionManager.jsonFormat.decodeFromString(Vision.serializer(), text)
val renderer = findRendererFor(vision) ?: error("Could nof find renderer for $vision")
renderer.render(element, vision)
if (requestUpdates) {
val wsUrl = URL(endpoint).apply {
pathname += "/ws"
protocol = "ws"
searchParams.append("name", name)
}
val ws = WebSocket(wsUrl.toString()).apply {
onmessage = { messageEvent ->
val stringData: String? = messageEvent.data as? String
if (stringData != null) {
val update = visionManager.jsonFormat.decodeFromString(Vision.serializer(), text)
vision.update(update)
} else {
console.error("WebSocket message data is not a string")
}
}
onopen = {
console.info("WebSocket update channel established for output '$name'")
}
onclose = {
console.info("WebSocket update channel closed for output '$name'")
}
onerror = {
console.error("WebSocket update channel error for output '$name'")
}
}
}
}
} else {
console.error("Failed to fetch initial vision state from $endpoint")
}
}
}
public companion object : PluginFactory<VisionClient> {
override fun invoke(meta: Meta, context: Context): VisionClient = VisionClient()
override val tag: PluginTag = PluginTag(name = "vision.client", group = PluginTag.DATAFORGE_GROUP)
override val type: KClass<out VisionClient> = VisionClient::class
}
}
/**
* Fetch and render visions for all elements with [HtmlOutputScope.OUTPUT_CLASS] class inside given [element].
*/
public fun VisionClient.fetchVisionsInChildren(element: Element, requestUpdates: Boolean = true) {
val elements = element.getElementsByClassName(HtmlOutputScope.OUTPUT_CLASS)
console.info("Finished search for outputs. Found ${elements.length} items")
elements.asList().forEach { child ->
fetchAndRenderVision(child, requestUpdates)
}
}
/**
* Fetch visions from the server for all elements with [HtmlOutputScope.OUTPUT_CLASS] class in the document body
*/
public fun VisionClient.fetchAndRenderAllVisions(requestUpdates: Boolean = true){
val element = document.body ?: error("Document does not have a body")
fetchVisionsInChildren(element, requestUpdates)
}

View File

@ -1,7 +1,9 @@
package hep.dataforge.vision.client package hep.dataforge.vision.client
import hep.dataforge.meta.DFExperimental
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.names.toName import hep.dataforge.names.toName
import hep.dataforge.provider.Type
import hep.dataforge.vision.Vision import hep.dataforge.vision.Vision
import hep.dataforge.vision.html.BindingHtmlOutputScope import hep.dataforge.vision.html.BindingHtmlOutputScope
import hep.dataforge.vision.html.HtmlOutputScope import hep.dataforge.vision.html.HtmlOutputScope
@ -10,18 +12,38 @@ import kotlinx.browser.document
import kotlinx.html.TagConsumer import kotlinx.html.TagConsumer
import org.w3c.dom.* import org.w3c.dom.*
public interface ElementVisionRenderer<in V : Vision> { @Type(ElementVisionRenderer.TYPE)
public fun render(element: Element, vision: V): Unit public interface ElementVisionRenderer {
}
public fun <V : Vision> Map<String, V>.bind(renderer: ElementVisionRenderer<V>) { /**
forEach { (id, vision) -> * Give a [vision] integer rating based on this renderer capabilities. [ZERO_RATING] or negative values means that this renderer
val element = document.getElementById(id) ?: error("Could not find element with id $id") * can't process a vision. The value of [DEFAULT_RATING] used for default renderer. Specialized renderers could specify
renderer.render(element, vision) * higher value in order to "steal" rendering job
*/
public fun rateVision(vision: Vision): Int
/**
* Display the [vision] inside a given [element] replacing its current content
*/
public fun render(element: Element, vision: Vision): Unit
public companion object {
public const val TYPE: String = "elementVisionRenderer"
public const val ZERO_RATING: Int = 0
public const val DEFAULT_RATING: Int = 10
} }
} }
public fun <V : Vision> Element.renderVisions(renderer: ElementVisionRenderer<V>, visionProvider: (Name) -> V?) { @DFExperimental
public fun Map<String, Vision>.bind(rendererFactory: (Vision) -> ElementVisionRenderer) {
forEach { (id, vision) ->
val element = document.getElementById(id) ?: error("Could not find element with id $id")
rendererFactory(vision).render(element, vision)
}
}
@DFExperimental
public fun Element.renderAllVisions(visionProvider: (Name) -> Vision, rendererFactory: (Vision) -> ElementVisionRenderer) {
val elements = getElementsByClassName(HtmlOutputScope.OUTPUT_CLASS) val elements = getElementsByClassName(HtmlOutputScope.OUTPUT_CLASS)
elements.asList().forEach { element -> elements.asList().forEach { element ->
val name = element.attributes[HtmlOutputScope.OUTPUT_NAME_ATTRIBUTE]?.value val name = element.attributes[HtmlOutputScope.OUTPUT_NAME_ATTRIBUTE]?.value
@ -30,21 +52,19 @@ public fun <V : Vision> Element.renderVisions(renderer: ElementVisionRenderer<V>
return@forEach return@forEach
} }
val vision = visionProvider(name.toName()) val vision = visionProvider(name.toName())
if (vision == null) { rendererFactory(vision).render(element, vision)
console.error("Vision with name $name is not resolved")
return@forEach
}
renderer.render(element, vision)
} }
} }
public fun <V : Vision> Document.renderVisions(renderer: ElementVisionRenderer<V>, visionProvider: (Name) -> V?): Unit { @DFExperimental
documentElement?.renderVisions(renderer, visionProvider) public fun Document.renderAllVisions(visionProvider: (Name) -> Vision, rendererFactory: (Vision) -> ElementVisionRenderer): Unit {
documentElement?.renderAllVisions(visionProvider,rendererFactory)
} }
@DFExperimental
public fun HtmlVisionFragment<Vision>.renderInDocument( public fun HtmlVisionFragment<Vision>.renderInDocument(
root: TagConsumer<HTMLElement>, root: TagConsumer<HTMLElement>,
renderer: ElementVisionRenderer<Vision>, renderer: ElementVisionRenderer,
): HTMLElement = BindingHtmlOutputScope<HTMLElement, Vision>(root).apply(content).let { scope -> ): HTMLElement = BindingHtmlOutputScope<HTMLElement, Vision>(root).apply(content).let { scope ->
scope.finalize().apply { scope.finalize().apply {
scope.bindings.forEach { (name, vision) -> scope.bindings.forEach { (name, vision) ->

View File

@ -14,6 +14,7 @@ import hep.dataforge.vision.html.*
import hep.dataforge.vision.server.VisionServer.Companion.DEFAULT_PAGE import hep.dataforge.vision.server.VisionServer.Companion.DEFAULT_PAGE
import io.ktor.application.* import io.ktor.application.*
import io.ktor.features.CORS import io.ktor.features.CORS
import io.ktor.features.CallLogging
import io.ktor.html.respondHtml import io.ktor.html.respondHtml
import io.ktor.http.ContentType import io.ktor.http.ContentType
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
@ -23,7 +24,10 @@ import io.ktor.http.content.static
import io.ktor.http.withCharset import io.ktor.http.withCharset
import io.ktor.response.respond import io.ktor.response.respond
import io.ktor.response.respondText import io.ktor.response.respondText
import io.ktor.routing.* import io.ktor.routing.application
import io.ktor.routing.get
import io.ktor.routing.route
import io.ktor.routing.routing
import io.ktor.server.engine.ApplicationEngine import io.ktor.server.engine.ApplicationEngine
import io.ktor.websocket.WebSockets import io.ktor.websocket.WebSockets
import io.ktor.websocket.webSocket import io.ktor.websocket.webSocket
@ -34,9 +38,12 @@ import java.awt.Desktop
import java.net.URI import java.net.URI
import kotlin.time.milliseconds import kotlin.time.milliseconds
/**
* A ktor plugin container with given [routing]
*/
public class VisionServer internal constructor( public class VisionServer internal constructor(
private val visionManager: VisionManager, private val visionManager: VisionManager,
private val routing: Routing, private val application: Application,
private val rootRoute: String, private val rootRoute: String,
) : Configurable { ) : Configurable {
override val config: Config = Config() override val config: Config = Config()
@ -57,7 +64,7 @@ public class VisionServer internal constructor(
title: String, title: String,
headers: List<HtmlFragment>, headers: List<HtmlFragment>,
): Map<Name, Vision> { ): Map<Name, Vision> {
lateinit var result: Map<Name, Vision> lateinit var visionMap: Map<Name, Vision>
head { head {
meta { meta {
@ -69,21 +76,26 @@ public class VisionServer internal constructor(
title(title) title(title)
} }
body { body {
result = visionFragment(visionFragment) // attributes[OUTPUT_ENDPOINT_ATTRIBUTE] = if (rootRoute.endsWith("/")) {
script { // rootRoute
type = "text/javascript" // } else {
// "$rootRoute/"
val normalizedRoute = if (rootRoute.endsWith("/")) { // }
rootRoute //Load the fragment and remember all loaded visions
} else { visionMap = visionFragment(visionFragment)
"$rootRoute/" // //The script runs when all headers already with required libraries are already loaded
} // script {
// type = "text/javascript"
src = TODO()//"${normalizedRoute}js/plotlyConnect.js" //
} // val normalizedRoute =
// unsafe {
// //language=JavaScript
// +"fetchAndRenderAllVisions()"
// }
// }
} }
return result return visionMap
} }
public fun page( public fun page(
@ -102,56 +114,50 @@ public class VisionServer internal constructor(
null null
} }
routing.createRouteFromPath(rootRoute).apply { application.routing {
route(route) { route(rootRoute) {
//Update websocket route(route) {
webSocket("ws") { //Update websocket
val name: String = call.request.queryParameters["name"] webSocket("ws") {
?: error("Vision name is not defined in parameters") val name: String = call.request.queryParameters["name"]
?: error("Vision name is not defined in parameters")
application.log.debug("Opened server socket for $name") application.log.debug("Opened server socket for $name")
val vision: Vision = visions[name.toName()] ?: error("Plot with id='$name' not registered") val vision: Vision = visions[name.toName()] ?: error("Plot with id='$name' not registered")
try { try {
vision.flowChanges(this, updateInterval.milliseconds).collect { update -> vision.flowChanges(this, updateInterval.milliseconds).collect { update ->
val json = visionManager.encodeToString(update) val json = visionManager.encodeToString(update)
outgoing.send(Frame.Text(json)) outgoing.send(Frame.Text(json))
}
} catch (ex: Exception) {
application.log.debug("Closed server socket for $name")
} }
} catch (ex: Exception) {
application.log.debug("Closed server socket for $name")
} }
} //Plots in their json representation
//Plots in their json representation get("vision") {
get("vision") { val name: String = call.request.queryParameters["name"]
val name: String = call.request.queryParameters["name"] ?: error("Vision name is not defined in parameters")
?: error("Vision name is not defined in parameters")
val vision: Vision? = visions[name.toName()] val vision: Vision? = visions[name.toName()]
if (vision == null) { if (vision == null) {
call.respond(HttpStatusCode.NotFound, "Vision with name '$name' not found") call.respond(HttpStatusCode.NotFound, "Vision with name '$name' not found")
} else { } else {
call.respondText( call.respondText(
visionManager.encodeToString(vision), visionManager.encodeToString(vision),
contentType = ContentType.Application.Json, contentType = ContentType.Application.Json,
status = HttpStatusCode.OK status = HttpStatusCode.OK
) )
} }
} }
//filled pages //filled pages
get { get {
// val origin = call.request.origin if (cachedHtml == null) {
// val url = URLBuilder().apply { call.respondHtml {
// protocol = URLProtocol.createOrDefault(origin.scheme) visions.putAll(buildPage(visionFragment, title, headers))
// //workaround for https://github.com/ktorio/ktor/issues/1663 }
// host = if (origin.host.startsWith("0:")) "[${origin.host}]" else origin.host } else {
// port = origin.port call.respondText(cachedHtml, ContentType.Text.Html.withCharset(Charsets.UTF_8))
// encodedPath = origin.uri
// }.build()
if (cachedHtml == null) {
call.respondHtml {
visions.putAll(buildPage(visionFragment, title, headers))
} }
} else {
call.respondText(cachedHtml, ContentType.Text.Html.withCharset(Charsets.UTF_8))
} }
} }
} }
@ -160,7 +166,7 @@ public class VisionServer internal constructor(
public fun page( public fun page(
route: String = DEFAULT_PAGE, route: String = DEFAULT_PAGE,
title: String = "Plotly server page '$route'", title: String = "VisionForge server page '$route'",
headers: List<HtmlFragment> = emptyList(), headers: List<HtmlFragment> = emptyList(),
content: HtmlOutputScope<*, Vision>.() -> Unit, content: HtmlOutputScope<*, Vision>.() -> Unit,
) { ) {
@ -170,7 +176,6 @@ public class VisionServer internal constructor(
public companion object { public companion object {
public const val DEFAULT_PAGE: String = "/" public const val DEFAULT_PAGE: String = "/"
public val UPDATE_MODE_KEY: Name = "update.mode".toName()
public val UPDATE_INTERVAL_KEY: Name = "update.interval".toName() public val UPDATE_INTERVAL_KEY: Name = "update.interval".toName()
} }
} }
@ -190,8 +195,12 @@ public fun Application.visionModule(context: Context, route: String = DEFAULT_PA
} }
} }
if(featureOrNull(CallLogging) == null){
install(CallLogging)
}
val routing = routing {
routing {
route(route) { route(route) {
static { static {
resources() resources()
@ -201,7 +210,7 @@ public fun Application.visionModule(context: Context, route: String = DEFAULT_PA
val visionManager = context.plugins.fetch(VisionManager) val visionManager = context.plugins.fetch(VisionManager)
return VisionServer(visionManager, routing, route) return VisionServer(visionManager, this, route)
} }
public fun ApplicationEngine.show() { public fun ApplicationEngine.show() {

View File

@ -12,7 +12,7 @@ public class Box(
public val xSize: Float, public val xSize: Float,
public val ySize: Float, public val ySize: Float,
public val zSize: Float public val zSize: Float
) : BasicSolid(), GeometrySolid { ) : SolidBase(), GeometrySolid {
//TODO add helper for color configuration //TODO add helper for color configuration
override fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>) { override fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>) {

View File

@ -19,7 +19,7 @@ public class Composite(
public val compositeType: CompositeType, public val compositeType: CompositeType,
public val first: Solid, public val first: Solid,
public val second: Solid public val second: Solid
) : BasicSolid(), Solid, VisionGroup { ) : SolidBase(), Solid, VisionGroup {
init { init {
first.parent = this first.parent = this

View File

@ -18,7 +18,7 @@ public class ConeSegment(
public var upperRadius: Float, public var upperRadius: Float,
public var startAngle: Float = 0f, public var startAngle: Float = 0f,
public var angle: Float = PI2 public var angle: Float = PI2
) : BasicSolid(), GeometrySolid { ) : SolidBase(), GeometrySolid {
override fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>) { override fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>) {
val segments = detail ?: 8 val segments = detail ?: 8

View File

@ -7,7 +7,7 @@ import kotlinx.serialization.Serializable
@Serializable @Serializable
@SerialName("solid.convex") @SerialName("solid.convex")
public class Convex(public val points: List<Point3D>) : BasicSolid(), Solid public class Convex(public val points: List<Point3D>) : SolidBase(), Solid
public inline fun VisionContainerBuilder<Solid>.convex(name: String = "", action: ConvexBuilder.() -> Unit = {}): Convex = public inline fun VisionContainerBuilder<Solid>.convex(name: String = "", action: ConvexBuilder.() -> Unit = {}): Convex =
ConvexBuilder().apply(action).build().also { set(name, it) } ConvexBuilder().apply(action).build().also { set(name, it) }

View File

@ -39,7 +39,7 @@ public data class Layer(var x: Float, var y: Float, var z: Float, var scale: Flo
public class Extruded( public class Extruded(
public var shape: List<Point2D> = ArrayList(), public var shape: List<Point2D> = ArrayList(),
public var layers: MutableList<Layer> = ArrayList() public var layers: MutableList<Layer> = ArrayList()
) : BasicSolid(), GeometrySolid { ) : SolidBase(), GeometrySolid {
public fun shape(block: Shape2DBuilder.() -> Unit) { public fun shape(block: Shape2DBuilder.() -> Unit) {
this.shape = Shape2DBuilder().apply(block).build() this.shape = Shape2DBuilder().apply(block).build()

View File

@ -12,7 +12,7 @@ import kotlinx.serialization.Serializable
@Serializable @Serializable
@SerialName("solid.line") @SerialName("solid.line")
public class PolyLine(public var points: List<Point3D>) : BasicSolid(), Solid { public class PolyLine(public var points: List<Point3D>) : SolidBase(), Solid {
//var lineType by string() //var lineType by string()
public var thickness: Number by props().number(1.0, key = SolidMaterial.MATERIAL_KEY + THICKNESS_KEY) public var thickness: Number by props().number(1.0, key = SolidMaterial.MATERIAL_KEY + THICKNESS_KEY)

View File

@ -12,7 +12,7 @@ import kotlinx.serialization.Serializable
@Serializable @Serializable
@SerialName("solid") @SerialName("solid")
public open class BasicSolid : VisionBase(), Solid { public open class SolidBase : VisionBase(), Solid {
override val descriptor: NodeDescriptor get() = Solid.descriptor override val descriptor: NodeDescriptor get() = Solid.descriptor
override var position: Point3D? = null override var position: Point3D? = null

View File

@ -11,7 +11,7 @@ public class SolidLabel(
public var text: String, public var text: String,
public var fontSize: Double, public var fontSize: Double,
public var fontFamily: String, public var fontFamily: String,
) : BasicSolid(), Solid ) : SolidBase(), Solid
public fun VisionContainerBuilder<Solid>.label( public fun VisionContainerBuilder<Solid>.label(
text: String, text: String,

View File

@ -7,10 +7,7 @@ import hep.dataforge.context.PluginTag
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.names.toName import hep.dataforge.names.toName
import hep.dataforge.vision.Vision import hep.dataforge.vision.*
import hep.dataforge.vision.VisionGroup
import hep.dataforge.vision.VisionGroupBase
import hep.dataforge.vision.VisionManager
import hep.dataforge.vision.VisionManager.Companion.VISION_SERIALIZER_MODULE_TARGET import hep.dataforge.vision.VisionManager.Companion.VISION_SERIALIZER_MODULE_TARGET
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.PolymorphicModuleBuilder import kotlinx.serialization.modules.PolymorphicModuleBuilder
@ -42,6 +39,7 @@ public class SolidManager(meta: Meta) : AbstractPlugin(meta) {
subclass(Composite.serializer()) subclass(Composite.serializer())
subclass(Tube.serializer()) subclass(Tube.serializer())
subclass(Box.serializer()) subclass(Box.serializer())
subclass(ConeSegment.serializer())
subclass(Convex.serializer()) subclass(Convex.serializer())
subclass(Extruded.serializer()) subclass(Extruded.serializer())
subclass(PolyLine.serializer()) subclass(PolyLine.serializer())
@ -51,11 +49,13 @@ public class SolidManager(meta: Meta) : AbstractPlugin(meta) {
public val serializersModuleForSolids: SerializersModule = SerializersModule { public val serializersModuleForSolids: SerializersModule = SerializersModule {
polymorphic(Vision::class) { polymorphic(Vision::class) {
subclass(VisionBase.serializer())
subclass(VisionGroupBase.serializer()) subclass(VisionGroupBase.serializer())
solids() solids()
} }
polymorphic(Solid::class) { polymorphic(Solid::class) {
default { SolidBase.serializer() }
solids() solids()
} }
} }

View File

@ -9,7 +9,7 @@ import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient import kotlinx.serialization.Transient
import kotlin.collections.set import kotlin.collections.set
public abstract class AbstractReference : BasicSolid(), VisionGroup { public abstract class AbstractReference : SolidBase(), VisionGroup {
public abstract val prototype: Solid public abstract val prototype: Solid
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? = sequence { override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? = sequence {

View File

@ -16,7 +16,7 @@ public class Sphere(
public var phi: Float = PI2, public var phi: Float = PI2,
public var thetaStart: Float = 0f, public var thetaStart: Float = 0f,
public var theta: Float = PI.toFloat(), public var theta: Float = PI.toFloat(),
) : BasicSolid(), GeometrySolid { ) : SolidBase(), GeometrySolid {
override fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>) { override fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>) {
fun point3DfromSphCoord(r: Float, theta: Float, phi: Float): Point3D { fun point3DfromSphCoord(r: Float, theta: Float, phi: Float): Point3D {

View File

@ -19,7 +19,7 @@ public class Tube(
public var innerRadius: Float = 0f, public var innerRadius: Float = 0f,
public var startAngle: Float = 0f, public var startAngle: Float = 0f,
public var angle: Float = PI2, public var angle: Float = PI2,
) : BasicSolid(), GeometrySolid { ) : SolidBase(), GeometrySolid {
init { init {
require(radius > 0) require(radius > 0)

View File

@ -15,7 +15,7 @@ import kotlin.collections.set
import kotlin.reflect.KClass import kotlin.reflect.KClass
import info.laht.threekt.objects.Group as ThreeGroup import info.laht.threekt.objects.Group as ThreeGroup
public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer<Solid> { public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
override val tag: PluginTag get() = Companion.tag override val tag: PluginTag get() = Companion.tag
public val solidManager: SolidManager by require(SolidManager) public val solidManager: SolidManager by require(SolidManager)
@ -122,8 +122,19 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer<Solid> {
attach(element) attach(element)
} }
override fun render(element: Element, vision: Solid) { override fun content(target: String): Map<Name, Any> {
createCanvas(element).render(vision) return when (target) {
ElementVisionRenderer.TYPE -> mapOf("three".asName() to this)
else -> super.content(target)
}
}
override fun rateVision(vision: Vision): Int {
return if (vision is Solid) ElementVisionRenderer.DEFAULT_RATING else ElementVisionRenderer.ZERO_RATING
}
override fun render(element: Element, vision: Vision) {
createCanvas(element).render(vision as? Solid ?: error("Only solids are rendered"))
} }
public companion object : PluginFactory<ThreePlugin> { public companion object : PluginFactory<ThreePlugin> {

View File

@ -1,11 +1,11 @@
package hep.dataforge.vision.solid.three package hep.dataforge.vision.solid.three
import hep.dataforge.vision.solid.BasicSolid import hep.dataforge.vision.solid.SolidBase
import info.laht.threekt.core.Object3D import info.laht.threekt.core.Object3D
/** /**
* A custom visual object that has its own Three.js renderer * A custom visual object that has its own Three.js renderer
*/ */
public abstract class ThreeVision : BasicSolid() { public abstract class ThreeVision : SolidBase() {
public abstract fun render(): Object3D public abstract fun render(): Object3D
} }