[WIP] great refactoring in progress

This commit is contained in:
Alexander Nozik 2022-08-04 21:36:00 +03:00
parent 4b1149b99b
commit 791d6d7a81
No known key found for this signature in database
GPG Key ID: F7FCF2DD25C71357
97 changed files with 1316 additions and 1181 deletions

View File

@ -3,7 +3,7 @@ plugins {
// id("org.jetbrains.kotlinx.kover") version "0.5.0"
}
val dataforgeVersion by extra("0.6.0-dev-10")
val dataforgeVersion by extra("0.6.0-dev-12")
val fxVersion by extra("11")
allprojects{

View File

@ -1,10 +1,14 @@
package ru.mipt.npm.root
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.double
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.int
import space.kscience.dataforge.meta.isEmpty
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.plus
import space.kscience.dataforge.values.doubleArray
import space.kscience.visionforge.isEmpty
import space.kscience.visionforge.setPropertyValue
import space.kscience.visionforge.solid.*
import space.kscience.visionforge.solid.SolidMaterial.Companion.MATERIAL_COLOR_KEY
import kotlin.math.*
@ -20,7 +24,7 @@ private fun degToRad(d: Double) = d * PI / 180.0
private data class RootToSolidContext(
val prototypeHolder: PrototypeHolder,
val currentLayer: Int = 0,
val maxLayer: Int = 5
val maxLayer: Int = 5,
)
// converting to XYZ to TaitBryan angles according to https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix
@ -42,14 +46,17 @@ private fun Solid.useMatrix(matrix: DGeoMatrix?) {
"TGeoIdentity" -> {
//do nothing
}
"TGeoTranslation" -> {
val fTranslation by matrix.meta.doubleArray()
translate(fTranslation)
}
"TGeoRotation" -> {
val fRotationMatrix by matrix.meta.doubleArray()
rotate(fRotationMatrix)
}
"TGeoCombiTrans" -> {
val fTranslation by matrix.meta.doubleArray()
@ -58,6 +65,7 @@ private fun Solid.useMatrix(matrix: DGeoMatrix?) {
rotate(it.doubleArray)
}
}
"TGeoHMatrix" -> {
val fTranslation by matrix.meta.doubleArray()
val fRotationMatrix by matrix.meta.doubleArray()
@ -73,7 +81,7 @@ private fun SolidGroup.addShape(
shape: DGeoShape,
context: RootToSolidContext,
name: String? = shape.fName.ifEmpty { null },
block: Solid.() -> Unit = {}
block: Solid.() -> Unit = {},
) {
when (shape.typename) {
"TGeoCompositeShape" -> {
@ -94,6 +102,7 @@ private fun SolidGroup.addShape(
}
}.apply(block)
}
"TGeoXtru" -> {
val fNvert by shape.meta.int(0)
val fX by shape.meta.doubleArray()
@ -121,6 +130,7 @@ private fun SolidGroup.addShape(
}
}.apply(block)
}
"TGeoTube" -> {
val fRmax by shape.meta.double(0.0)
val fDz by shape.meta.double(0.0)
@ -134,6 +144,7 @@ private fun SolidGroup.addShape(
block = block
)
}
"TGeoTubeSeg" -> {
val fRmax by shape.meta.double(0.0)
val fDz by shape.meta.double(0.0)
@ -151,6 +162,7 @@ private fun SolidGroup.addShape(
block = block
)
}
"TGeoPcon" -> {
val fDphi by shape.meta.double(0.0)
val fNz by shape.meta.int(2)
@ -176,6 +188,7 @@ private fun SolidGroup.addShape(
TODO()
}
}
"TGeoPgon" -> {
//TODO add a inner polygone layer
val fDphi by shape.meta.double(0.0)
@ -206,15 +219,18 @@ private fun SolidGroup.addShape(
}
}.apply(block)
}
"TGeoShapeAssembly" -> {
val fVolume by shape.dObject(::DGeoVolume)
fVolume?.let { volume ->
addRootVolume(volume, context, block = block)
}
}
"TGeoBBox" -> {
box(shape.fDX * 2, shape.fDY * 2, shape.fDZ * 2, name = name, block = block)
}
"TGeoTrap" -> {
val fTheta by shape.meta.double(0.0)
val fPhi by shape.meta.double(0.0)
@ -242,6 +258,7 @@ private fun SolidGroup.addShape(
val node8 = Point3D(-fTl2, fH2, fDz)
hexagon(node1, node2, node3, node4, node5, node6, node7, node8, name)
}
"TGeoScaledShape" -> {
val fShape by shape.dObject(::DGeoShape)
val fScale by shape.dObject(::DGeoScale)
@ -253,6 +270,7 @@ private fun SolidGroup.addShape(
}
}
}
else -> {
TODO("A shape with type ${shape.typename} not implemented")
}
@ -267,6 +285,7 @@ private fun SolidGroup.addRootNode(obj: DGeoNode, context: RootToSolidContext) {
val fMatrix by obj.dObject(::DGeoMatrix)
this.useMatrix(fMatrix)
}
"TGeoNodeOffset" -> {
val fOffset by obj.meta.double(0.0)
x = fOffset
@ -301,10 +320,10 @@ private fun buildVolume(volume: DGeoVolume, context: RootToSolidContext): Solid?
}
}
}
return if (group.isEmpty()) {
return if (group.children.isEmpty()) {
null
} else if (group.children.size == 1 && group.meta.isEmpty()) {
(group.children.values.first() as Solid).apply { parent = null }
} else if (group.items.size == 1 && group.meta.isEmpty()) {
group.items.values.first().apply { parent = null }
} else {
group
}
@ -317,7 +336,7 @@ private fun SolidGroup.addRootVolume(
context: RootToSolidContext,
name: String? = null,
cache: Boolean = true,
block: Solid.() -> Unit = {}
block: Solid.() -> Unit = {},
) {
val combinedName = if (volume.fName.isEmpty()) {
name
@ -330,7 +349,7 @@ private fun SolidGroup.addRootVolume(
if (!cache) {
val group = buildVolume(volume, context)?.apply {
volume.fFillColor?.let {
meta[MATERIAL_COLOR_KEY] = RootColors[it]
setPropertyValue(MATERIAL_COLOR_KEY, RootColors[it])
}
block()
}
@ -347,7 +366,7 @@ private fun SolidGroup.addRootVolume(
ref(templateName, name).apply {
volume.fFillColor?.let {
meta[MATERIAL_COLOR_KEY] = RootColors[it]
setPropertyValue(MATERIAL_COLOR_KEY, RootColors[it])
}
block()
}

View File

@ -160,7 +160,7 @@ private fun SolidGroup.volume(volume: TGeoVolume, name: String? = null, cache: B
name = combinedName,
obj = group,
prototypeHolder = rootPrototypes,
templateName = volumesName + Name.parse(combinedName ?: "volume[${group.hashCode()}]")
prototypeName = volumesName + Name.parse(combinedName ?: "volume[${group.hashCode()}]")
)
}

View File

@ -1,13 +1,13 @@
package space.kscience.visionforge.gdml
import space.kscience.dataforge.meta.string
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.values.asValue
import space.kscience.dataforge.values.string
import space.kscience.gdml.GdmlShowCase
import space.kscience.visionforge.Vision
import space.kscience.visionforge.computeProperties
import space.kscience.visionforge.get
import space.kscience.visionforge.setProperty
import space.kscience.visionforge.getProperty
import space.kscience.visionforge.getPropertyValue
import space.kscience.visionforge.solid.Solid
import space.kscience.visionforge.solid.SolidMaterial
import space.kscience.visionforge.solid.material
@ -20,8 +20,8 @@ class GDMLVisionTest {
@Test
fun testCubesStyles(){
val segment = cubes["composite-000.segment-0"] as Solid
println(segment.computeProperties().getValue(Vision.STYLE_KEY))
val segment = cubes.children["composite-000.segment-0"] as Solid
println(segment.getPropertyValue(Vision.STYLE_KEY))
// println(segment.computePropertyNode(SolidMaterial.MATERIAL_KEY))
// println(segment.computeProperty(SolidMaterial.MATERIAL_COLOR_KEY))
@ -35,7 +35,7 @@ class GDMLVisionTest {
fun testPrototypeProperty() {
val child = cubes[Name.of("composite-000","segment-0")]
assertNotNull(child)
child.setProperty(SolidMaterial.MATERIAL_COLOR_KEY, "red".asValue())
assertEquals("red", child.getPropertyValue(SolidMaterial.MATERIAL_COLOR_KEY)?.string)
child.setPropertyValue(SolidMaterial.MATERIAL_COLOR_KEY, "red".asValue())
assertEquals("red", child.getProperty(SolidMaterial.MATERIAL_COLOR_KEY).string)
}
}

View File

@ -26,7 +26,6 @@ import space.kscience.visionforge.setAsRoot
import space.kscience.visionforge.solid.Solid
import space.kscience.visionforge.solid.Solids
import space.kscience.visionforge.solid.ambientLight
import space.kscience.visionforge.solid.invoke
import styled.css
import styled.styledDiv

View File

@ -10,7 +10,6 @@ import space.kscience.visionforge.Colors
import space.kscience.visionforge.gdml.toVision
import space.kscience.visionforge.react.render
import space.kscience.visionforge.solid.ambientLight
import space.kscience.visionforge.solid.invoke
import space.kscience.visionforge.solid.three.ThreePlugin
import space.kscience.visionforge.startApplication
import styled.injectGlobal

View File

@ -75,7 +75,7 @@ private class JsPlaygroundApp : Application {
context = playgroundContext
solid {
ambientLight {
color(Colors.white)
color.set(Colors.white)
}
repeat(100) {
sphere(5, name = "sphere[$it]") {
@ -83,7 +83,7 @@ private class JsPlaygroundApp : Application {
y = random.nextDouble(-300.0, 300.0)
z = random.nextDouble(-300.0, 300.0)
material {
color(random.nextInt())
color.set(random.nextInt())
}
detail = 16
}

View File

@ -43,13 +43,13 @@ val GravityDemo = fc<DemoProps> { props ->
context = props.context
solid {
pointLight(200, 200, 200, name = "light"){
color(Colors.white)
color.set(Colors.white)
}
ambientLight()
sphere(5.0, "ball") {
detail = 16
color("red")
color.set("red")
val h = 100.0
y = h
context.launch {

View File

@ -3,24 +3,18 @@ package ru.mipt.npm.muon.monitor
import ru.mipt.npm.muon.monitor.Monitor.CENTRAL_LAYER_Z
import ru.mipt.npm.muon.monitor.Monitor.LOWER_LAYER_Z
import ru.mipt.npm.muon.monitor.Monitor.UPPER_LAYER_Z
import space.kscience.visionforge.VisionContainerBuilder
import space.kscience.visionforge.VisionManager
import space.kscience.visionforge.removeAll
import space.kscience.visionforge.setAsRoot
import space.kscience.visionforge.setProperty
import space.kscience.visionforge.solid.*
import kotlin.collections.HashMap
import kotlin.collections.HashSet
import kotlin.collections.filter
import kotlin.collections.forEach
import kotlin.collections.set
import kotlin.collections.toTypedArray
import kotlin.math.PI
class Model(val manager: VisionManager) {
private val map = HashMap<String, SolidGroup>()
private val events = HashSet<Event>()
private fun SolidGroup.pixel(pixel: SC1) {
private fun VisionContainerBuilder<Solid>.pixel(pixel: SC1) {
val group = group(pixel.name) {
position = Point3D(pixel.center.x, pixel.center.y, pixel.center.z)
box(pixel.xSize, pixel.ySize, pixel.zSize)
@ -45,7 +39,7 @@ class Model(val manager: VisionManager) {
val root: SolidGroup = SolidGroup().apply {
setAsRoot(this@Model.manager)
material {
color("darkgreen")
color.set("darkgreen")
}
rotationX = PI / 2
group("bottom") {
@ -70,14 +64,14 @@ class Model(val manager: VisionManager) {
private fun highlight(pixel: String) {
println("highlight $pixel")
map[pixel]?.color?.invoke("blue")
map[pixel]?.color.set("blue")
}
fun reset() {
map.values.forEach {
it.setProperty(SolidMaterial.MATERIAL_COLOR_KEY, null)
it.setPropertyValue(SolidMaterial.MATERIAL_COLOR_KEY, null)
}
tracks.removeAll()
tracks.children.clear()
}
fun displayEvent(event: Event) {
@ -89,7 +83,7 @@ class Model(val manager: VisionManager) {
event.track?.let {
tracks.polyline(*it.toTypedArray(), name = "track[${event.id}]") {
thickness = 4
color("red")
color.set("red")
}
}
}

View File

@ -25,7 +25,6 @@ import space.kscience.visionforge.react.flexRow
import space.kscience.visionforge.ring.ThreeCanvasWithControls
import space.kscience.visionforge.ring.tab
import space.kscience.visionforge.solid.ambientLight
import space.kscience.visionforge.solid.invoke
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
import space.kscience.visionforge.solid.three.edges
import styled.css

View File

@ -7,7 +7,7 @@ import space.kscience.visionforge.gdml.toVision
import space.kscience.visionforge.html.ResourceLocation
import space.kscience.visionforge.solid.Solids
import space.kscience.visionforge.solid.color
import space.kscience.visionforge.solid.invoke
import space.kscience.visionforge.solid.set
import space.kscience.visionforge.visible
import java.nio.file.Path
@ -229,7 +229,7 @@ fun main() = makeVisionFile(Path.of("curves.html"), resourceLocation = ResourceL
visible = false
}
if(solid.name.startsWith("gas")){
color("green")
color.set("green")
} else {
//make all solids semi-transparent
transparent()

View File

@ -5,7 +5,7 @@ import space.kscience.visionforge.Colors
import space.kscience.visionforge.gdml.gdml
import space.kscience.visionforge.solid.Solids
import space.kscience.visionforge.solid.ambientLight
import space.kscience.visionforge.solid.invoke
import space.kscience.visionforge.solid.set
import space.kscience.visionforge.solid.solid
fun main() = makeVisionFile {
@ -13,7 +13,7 @@ fun main() = makeVisionFile {
requirePlugin(Solids)
solid {
ambientLight {
color(Colors.white)
color.set(Colors.white)
}
gdml(GdmlShowCase.babyIaxo(), "D0")
}

View File

@ -19,7 +19,7 @@ fun main() = makeVisionFile(
vision {
solid {
ambientLight {
color(Colors.white)
color.set(Colors.white)
}
repeat(100) {
sphere(5, name = "sphere[$it]") {
@ -27,7 +27,7 @@ fun main() = makeVisionFile(
y = random.nextDouble(-300.0, 300.0)
z = random.nextDouble(-300.0, 300.0)
material {
color(random.nextInt())
color.set(random.nextInt())
}
detail = 16
}

View File

@ -2,8 +2,8 @@ package space.kscience.visionforge.examples
import space.kscience.visionforge.html.ResourceLocation
import space.kscience.visionforge.solid.box
import space.kscience.visionforge.solid.invoke
import space.kscience.visionforge.solid.material
import space.kscience.visionforge.solid.set
import space.kscience.visionforge.solid.solid
fun main() = makeVisionFile(resourceLocation = ResourceLocation.SYSTEM) {
@ -11,7 +11,7 @@ fun main() = makeVisionFile(resourceLocation = ResourceLocation.SYSTEM) {
solid {
box(100, 100, 100)
material {
emissiveColor("red")
emissiveColor.set("red")
}
}
}

View File

@ -15,7 +15,7 @@ internal fun visionOfSatellite(
ySegmentSize: Number = xSegmentSize,
fiberDiameter: Number = 1.0,
): SolidGroup = SolidGroup {
color("darkgreen")
color.set("darkgreen")
val transparent by style {
this[SolidMaterial.MATERIAL_OPACITY_KEY] = 0.3
}

View File

@ -44,7 +44,7 @@ fun main() {
val randomJ = Random.nextInt(1, 4)
val target = Name.parse("layer[$randomLayer].segment[$randomI,$randomJ]")
val targetVision = sat[target] as Solid
targetVision.color("red")
targetVision.color.set("red")
delay(1000)
targetVision.color.clear()
delay(500)

View File

@ -20,7 +20,7 @@ fun VisionLayout<Solid>.demo(name: String, title: String = name, block: SolidGro
}
val vision = SolidGroup(block).apply {
ambientLight{
color(Colors.white)
color.set(Colors.white)
}
}
render(Name.parse(name), vision, meta)
@ -46,23 +46,23 @@ fun VisionLayout<Solid>.showcase() {
ambientLight()
box(100.0, 100.0, 100.0) {
z = -110.0
color("teal")
color.set("teal")
}
sphere(50.0) {
x = 110
detail = 16
color("red")
color.set("red")
}
tube(50, height = 10, innerRadius = 25, angle = PI) {
y = 110
detail = 16
rotationX = PI / 4
color("blue")
color.set("blue")
}
sphereLayer(50, 40, theta = PI / 2) {
rotationX = -PI * 3 / 4
z = 110
color(Colors.pink)
color.set(Colors.pink)
}
}
@ -77,7 +77,7 @@ fun VisionLayout<Solid>.showcase() {
visible = false
x = 110.0
//override color for this cube
color(1530)
color.set(1530)
GlobalScope.launch(Dispatchers.Main) {
while (isActive) {
@ -92,7 +92,7 @@ fun VisionLayout<Solid>.showcase() {
val random = Random(111)
while (isActive) {
delay(1000)
group.color(random.nextInt(0, Int.MAX_VALUE))
group.color.set(random.nextInt(0, Int.MAX_VALUE))
}
}
}
@ -104,7 +104,7 @@ fun VisionLayout<Solid>.showcase() {
rotationY = PI / 4
box(100, 100, 100) {
rotationZ = PI / 4
color(Colors.red)
color.set(Colors.red)
}
}
}
@ -117,7 +117,7 @@ fun VisionLayout<Solid>.showcase() {
for (i in 0..100) {
layer(i * 5, 20 * sin(2 * PI / 100 * i), 20 * cos(2 * PI / 100 * i))
}
color(Colors.teal)
color.set(Colors.teal)
rotationX = -PI / 2
}
}
@ -126,13 +126,13 @@ fun VisionLayout<Solid>.showcase() {
sphere(100) {
detail = 32
opacity = 0.4
color(Colors.blue)
color.set(Colors.blue)
}
repeat(20) {
polyline(Point3D(100, 100, 100), Point3D(-100, -100, -100)) {
thickness = 3.0
rotationX = it * PI2 / 20
color(Colors.green)
color.set(Colors.green)
//rotationY = it * PI2 / 20
}
}
@ -159,7 +159,7 @@ fun VisionLayout<Solid>.showcaseCSG() {
detail = 32
}
material {
color(Colors.pink)
color.set(Colors.pink)
}
}
composite(CompositeType.UNION) {
@ -169,7 +169,7 @@ fun VisionLayout<Solid>.showcaseCSG() {
sphere(50) {
detail = 32
}
color("lightgreen")
color.set("lightgreen")
opacity = 0.7
}
composite(CompositeType.SUBTRACT) {
@ -180,7 +180,7 @@ fun VisionLayout<Solid>.showcaseCSG() {
sphere(50) {
detail = 32
}
color("teal")
color.set("teal")
opacity = 0.7
}
}
@ -191,7 +191,7 @@ fun VisionLayout<Solid>.showcaseCSG() {
detail = 32
}
box(100, 100, 100)
color("red")
color.set("red")
opacity = 0.5
}
}

View File

@ -11,7 +11,6 @@ import space.kscience.dataforge.names.startsWith
import space.kscience.dataforge.values.asValue
import space.kscience.visionforge.onPropertyChange
import space.kscience.visionforge.set
import space.kscience.visionforge.setProperty
import space.kscience.visionforge.solid.SolidGroup
import space.kscience.visionforge.solid.layer
import space.kscience.visionforge.solid.three.*
@ -72,7 +71,7 @@ internal class VariableBox(val xSize: Number, val ySize: Number) : ThreeJsVision
var value: Int
get() = meta[VALUE].int ?: 0
set(value) {
setProperty(VALUE, value.asValue())
setPropertyValue(VALUE, value.asValue())
}
companion object {

View File

@ -3,7 +3,7 @@
![](../docs/images/hierarchy.png)
### Vision
* function `getPropertyValue(name: Name, inherit: Boolean = false, includeStyles: Boolean = true, includeDefaults: Boolean = true)` - get property value with given layer flags.
* function `getProperty(name: Name, inherit: Boolean = false, includeStyles: Boolean = true, includeDefaults: Boolean = true)` - get property value with given layer flags.
* function `setProperty(name: Name, item: Any?)` - a convenient method to set property node or value. If `item` is null, then node is removed, not a value
Sets the `item` property to the element with the `name` identification.

View File

@ -7,7 +7,7 @@ Properties, which can be inherited by objects, are `styles`, `prototypes` (if th
All values of `styles` property are contained in class `StyleSheet`, where they all are defined at `Group`s level. The `prototypes` property tree is defined in `SolidGroup` class via `PrototypeHolder` interface, and
`SolidReference` class helps to reuse a template object.
The order of inheritance of properties is set in function `getPropertyValue` in `VisionBase` class.
The order of inheritance of properties is set in function `getProperty` in `VisionBase` class.
The order is this:
* own styles
* prototypes

View File

@ -3,7 +3,7 @@
interface Vision{
val parent: VisionGroup?
fun getPropertyValue(name,inherit,includeStyles,includeDefaults): Value?
fun getProperty(name,inherit,includeStyles,includeDefaults): Value?
}
interface Solid{
@ -81,7 +81,7 @@ Solid <--- Composite
interface SolidReference{
val prototype: Solid
fun getPropertyValue(name,inherit,includeStyles,includeDefaults): Value?
fun getProperty(name,inherit,includeStyles,includeDefaults): Value?
}
VisionGroup <---- SolidReference
SolidReferenceGroup -- SolidReference
@ -91,7 +91,7 @@ class SolidReferenceGroup{
var properties: MutableMeta?
val prototype: Solid
val children: Map<NameToken, Vision>
fun getPropertyValue(name,inherit,includeStyles,includeDefaults): Value?
fun getProperty(name,inherit,includeStyles,includeDefaults): Value?
}
VisionBase <-- SolidReferenceGroup
VisionGroup <-- SolidReferenceGroup

View File

@ -6,4 +6,4 @@ kotlin.jupyter.add.scanner=false
org.gradle.parallel=true
org.gradle.jvmargs=-Xmx4G
toolsVersion=0.11.7-kotlin-1.7.0
toolsVersion=0.11.8-kotlin-1.7.10

View File

@ -10,8 +10,8 @@ import react.fc
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.isEmpty
import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionGroup
import space.kscience.visionforge.react.visionTree
import space.kscience.visionforge.solid.SolidGroup
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
import styled.css
import styled.styledDiv
@ -51,7 +51,7 @@ public val ThreeControls: FC<ThreeControlsProps> = fc { props ->
val selectedObject: Vision? = when {
selected == null -> null
selected.isEmpty() -> props.vision
else -> (props.vision as? VisionGroup)?.get(selected)
else -> (props.vision as? SolidGroup)?.get(selected)
}
if (selectedObject != null) {
visionPropertyEditor(selectedObject, key = selected)

View File

@ -2,13 +2,14 @@ package space.kscience.visionforge.bootstrap
import org.w3c.dom.Element
import react.RBuilder
import react.dom.render
import react.dom.client.createRoot
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.visionforge.Vision
import space.kscience.visionforge.computeProperties
import space.kscience.visionforge.getStyle
import space.kscience.visionforge.react.metaViewer
import space.kscience.visionforge.react.propertyEditor
import space.kscience.visionforge.react.render
import space.kscience.visionforge.solid.SolidReference
import space.kscience.visionforge.styles
@ -50,6 +51,6 @@ public fun RBuilder.visionPropertyEditor(
public fun Element.visionPropertyEditor(
item: Vision,
descriptor: MetaDescriptor? = item.descriptor,
): Unit = render(this) {
): Unit = createRoot(this).render {
visionPropertyEditor(item, descriptor = descriptor)
}

View File

@ -59,9 +59,9 @@ private fun RBuilder.visionTree(props: ObjectTreeProps): Unit {
val obj = props.obj
//display as node if any child is visible
if (obj is VisionGroup) {
if (obj is VisionGroup<*>) {
flexRow {
if (obj.children.any { !it.key.body.startsWith("@") }) {
if (obj.items.any { !it.key.body.startsWith("@") }) {
styledSpan {
css {
+TreeStyles.treeCaret
@ -81,9 +81,9 @@ private fun RBuilder.visionTree(props: ObjectTreeProps): Unit {
css {
+TreeStyles.tree
}
obj.children.entries
obj.items.entries
.filter { !it.key.toString().startsWith("@") } // ignore statics and other hidden children
.sortedBy { (it.value as? VisionGroup)?.isEmpty() ?: true } // ignore empty groups
.sortedBy { (it.value as? VisionGroup<*>)?.isEmpty() ?: true } // ignore empty groups
.forEach { (childToken, child) ->
styledDiv {
css {
@ -92,7 +92,7 @@ private fun RBuilder.visionTree(props: ObjectTreeProps): Unit {
child(ObjectTree) {
attrs {
this.name = props.name + childToken
this.obj = child
this.obj = child.vision
this.selected = props.selected
this.clickCallback = props.clickCallback
}

View File

@ -108,7 +108,7 @@ public val ThreeCanvasWithControls: FC<ThreeCanvasWithControlsProps> = fc("Three
selected?.let {
when {
it.isEmpty() -> solid
else -> (solid as? VisionGroup)?.get(it)
else -> (solid as? SolidGroup)?.get(it)
}
}
}
@ -165,7 +165,7 @@ public val ThreeCanvasWithControls: FC<ThreeCanvasWithControlsProps> = fc("Three
}
IslandContent {
propertyEditor(
ownProperties = vision.meta,
ownProperties = vision.properties(),
allProperties = vision.computeProperties(),
descriptor = vision.descriptor,
key = selected

View File

@ -183,7 +183,7 @@ public class space/kscience/visionforge/SimpleVisionPropertyContainer : space/ks
public fun <init> (Lspace/kscience/dataforge/meta/ObservableMutableMeta;)V
public synthetic fun getMeta ()Lspace/kscience/dataforge/meta/MutableMeta;
public fun getMeta ()Lspace/kscience/dataforge/meta/ObservableMutableMeta;
public fun getPropertyValue (Lspace/kscience/dataforge/names/Name;ZZZ)Lspace/kscience/dataforge/values/Value;
public fun getProperty (Lspace/kscience/dataforge/names/Name;ZZZ)Lspace/kscience/dataforge/values/Value;
}
public final class space/kscience/visionforge/StyleReference {
@ -241,8 +241,8 @@ public abstract interface class space/kscience/visionforge/Vision : space/kscien
public fun getManager ()Lspace/kscience/visionforge/VisionManager;
public abstract fun getMeta ()Lspace/kscience/dataforge/meta/ObservableMutableMeta;
public abstract fun getParent ()Lspace/kscience/visionforge/VisionGroup;
public abstract fun getPropertyValue (Lspace/kscience/dataforge/names/Name;ZZZ)Lspace/kscience/dataforge/values/Value;
public static synthetic fun getPropertyValue$default (Lspace/kscience/visionforge/Vision;Lspace/kscience/dataforge/names/Name;ZZZILjava/lang/Object;)Lspace/kscience/dataforge/values/Value;
public abstract fun getProperty (Lspace/kscience/dataforge/names/Name;ZZZ)Lspace/kscience/dataforge/values/Value;
public static synthetic fun getProperty$default (Lspace/kscience/visionforge/Vision;Lspace/kscience/dataforge/names/Name;ZZZILjava/lang/Object;)Lspace/kscience/dataforge/values/Value;
public abstract fun invalidateProperty (Lspace/kscience/dataforge/names/Name;)V
public abstract fun setParent (Lspace/kscience/visionforge/VisionGroup;)V
public abstract fun update (Lspace/kscience/visionforge/VisionChange;)V
@ -266,7 +266,7 @@ public class space/kscience/visionforge/VisionBase : space/kscience/visionforge/
protected final fun getOrCreateProperties ()Lspace/kscience/dataforge/meta/MutableMeta;
public fun getParent ()Lspace/kscience/visionforge/VisionGroup;
protected final fun getProperties ()Lspace/kscience/dataforge/meta/MutableMeta;
public fun getPropertyValue (Lspace/kscience/dataforge/names/Name;ZZZ)Lspace/kscience/dataforge/values/Value;
public fun getProperty (Lspace/kscience/dataforge/names/Name;ZZZ)Lspace/kscience/dataforge/values/Value;
public fun invalidateProperty (Lspace/kscience/dataforge/names/Name;)V
public fun setParent (Lspace/kscience/visionforge/VisionGroup;)V
protected final fun setProperties (Lspace/kscience/dataforge/meta/MutableMeta;)V
@ -446,8 +446,8 @@ public final class space/kscience/visionforge/VisionGroupKt {
public final class space/kscience/visionforge/VisionKt {
public static final fun getPropertyChanges (Lspace/kscience/visionforge/Vision;)Lkotlinx/coroutines/flow/Flow;
public static final fun getPropertyValue (Lspace/kscience/visionforge/Vision;Ljava/lang/String;ZZZ)Lspace/kscience/dataforge/values/Value;
public static synthetic fun getPropertyValue$default (Lspace/kscience/visionforge/Vision;Ljava/lang/String;ZZZILjava/lang/Object;)Lspace/kscience/dataforge/values/Value;
public static final fun getProperty (Lspace/kscience/visionforge/Vision;Ljava/lang/String;ZZZ)Lspace/kscience/dataforge/values/Value;
public static synthetic fun getProperty$default (Lspace/kscience/visionforge/Vision;Ljava/lang/String;ZZZILjava/lang/Object;)Lspace/kscience/dataforge/values/Value;
public static final fun getVisible (Lspace/kscience/visionforge/Vision;)Ljava/lang/Boolean;
public static final fun onPropertyChange (Lspace/kscience/visionforge/Vision;Lkotlin/jvm/functions/Function2;)V
public static final fun setProperty (Lspace/kscience/visionforge/Vision;Lspace/kscience/dataforge/names/Name;Ljava/lang/Object;)V
@ -499,8 +499,8 @@ public abstract class space/kscience/visionforge/VisionPlugin : space/kscience/d
public abstract interface class space/kscience/visionforge/VisionPropertyContainer {
public abstract fun getMeta ()Lspace/kscience/dataforge/meta/MutableMeta;
public abstract fun getPropertyValue (Lspace/kscience/dataforge/names/Name;ZZZ)Lspace/kscience/dataforge/values/Value;
public static synthetic fun getPropertyValue$default (Lspace/kscience/visionforge/VisionPropertyContainer;Lspace/kscience/dataforge/names/Name;ZZZILjava/lang/Object;)Lspace/kscience/dataforge/values/Value;
public abstract fun getProperty (Lspace/kscience/dataforge/names/Name;ZZZ)Lspace/kscience/dataforge/values/Value;
public static synthetic fun getProperty$default (Lspace/kscience/visionforge/VisionPropertyContainer;Lspace/kscience/dataforge/names/Name;ZZZILjava/lang/Object;)Lspace/kscience/dataforge/values/Value;
}
public final class space/kscience/visionforge/html/HeadersKt {

View File

@ -0,0 +1,107 @@
package space.kscience.visionforge
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MutableMeta
import space.kscience.dataforge.meta.asMutableMeta
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.isEmpty
import space.kscience.dataforge.values.Value
import space.kscience.visionforge.VisionGroup.Companion.updateProperties
import kotlin.jvm.Synchronized
@Serializable
public abstract class AbstractVision : Vision {
@Transient
override var parent: Vision? = null
protected var properties: MutableMeta? = null
override val meta: Meta get() = properties ?: Meta.EMPTY
@Synchronized
private fun getOrCreateProperties(): MutableMeta {
if (properties == null) {
//TODO check performance issues
val newProperties = MutableMeta()
properties = newProperties
}
return properties!!
}
@Transient
private val _propertyChanges = MutableSharedFlow<Name>()
override val propertyChanges: SharedFlow<Name> get() = _propertyChanges
override fun getPropertyValue(
name: Name,
inherit: Boolean,
includeStyles: Boolean,
includeDefaults: Boolean,
): Value? {
properties?.get(name)?.value?.let { return it }
if (includeStyles) {
getStyleProperty(name)?.value?.let { return it }
}
if (inherit) {
parent?.getPropertyValue(name, inherit, includeStyles, includeDefaults)?.let { return it }
}
if (includeDefaults) {
descriptor?.defaultNode?.get(name)?.value?.let { return it }
}
return null
}
override fun setProperty(name: Name, node: Meta?) {
//TODO check old value?
if (name.isEmpty()) {
properties = node?.asMutableMeta()
} else if (node == null) {
properties?.setMeta(name, node)
} else {
getOrCreateProperties().setMeta(name, node)
}
invalidateProperty(name)
}
override fun setPropertyValue(name: Name, value: Value?) {
//TODO check old value?
if (value == null) {
properties?.getMeta(name)?.value = null
} else {
getOrCreateProperties().setValue(name, value)
}
invalidateProperty(name)
}
override val descriptor: MetaDescriptor? get() = null
override fun invalidateProperty(propertyName: Name) {
if (propertyName == Vision.STYLE_KEY) {
styles.asSequence()
.mapNotNull { getStyle(it) }
.flatMap { it.items.asSequence() }
.distinctBy { it.key }
.forEach {
invalidateProperty(it.key.asName())
}
}
manager.context.launch {
_propertyChanges.emit(propertyName)
}
}
override fun update(change: VisionChange) {
change.properties?.let {
updateProperties(it, Name.EMPTY)
}
}
}

View File

@ -1,84 +0,0 @@
package space.kscience.visionforge
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.get
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.plus
import space.kscience.dataforge.values.MutableValueProvider
import space.kscience.dataforge.values.Value
private class ComputedVisionProperties(
val vision: Vision,
val pathName: Name,
val visionDescriptor: MetaDescriptor,
val parentInheritFlag: Boolean?,
val parentStylesFlag: Boolean?
) : Meta {
val descriptor: MetaDescriptor? by lazy { visionDescriptor[pathName] }
override val items: Map<NameToken, Meta>
get() {
val metaKeys = vision.meta.getMeta(pathName)?.items?.keys ?: emptySet()
val descriptorKeys = descriptor?.children?.map { NameToken(it.key) } ?: emptySet()
val inheritFlag = descriptor?.inherited ?: parentInheritFlag
val stylesFlag = descriptor?.usesStyles ?: parentStylesFlag
return (metaKeys + descriptorKeys).associateWith {
ComputedVisionProperties(
vision,
pathName + it,
visionDescriptor,
inheritFlag,
stylesFlag
)
}
}
override val value: Value?
get() {
val inheritFlag = descriptor?.inherited ?: parentInheritFlag ?: false
val stylesFlag = descriptor?.usesStyles ?: parentStylesFlag ?: true
return vision.getPropertyValue(pathName, inheritFlag, stylesFlag, true)
}
override fun toString(): String = Meta.toString(this)
override fun equals(other: Any?): Boolean = Meta.equals(this, other as? Meta)
override fun hashCode(): Int = Meta.hashCode(this)
}
/**
* Compute property node based on inheritance and style information from the descriptor
*/
public fun Vision.computeProperties(descriptor: MetaDescriptor? = this.descriptor): Meta =
if (descriptor == null) meta else ComputedVisionProperties(this, Name.EMPTY, descriptor, null, null)
public fun Vision.computePropertyNode(
name: Name,
descriptor: MetaDescriptor? = this.descriptor
): Meta? = computeProperties(descriptor)[name]
/**
* Compute the property based on the provided value descriptor. By default, use Vision own descriptor
*/
public fun Vision.computeProperty(name: Name, valueDescriptor: MetaDescriptor? = descriptor?.get(name)): Value? {
val inheritFlag = valueDescriptor?.inherited ?: false
val stylesFlag = valueDescriptor?.usesStyles ?: true
return getPropertyValue(name, inheritFlag, stylesFlag)
}
/**
* Accessor to all vision properties
*/
public fun Vision.computePropertyValues(
descriptor: MetaDescriptor? = this.descriptor
): MutableValueProvider = object : MutableValueProvider {
override fun getValue(name: Name): Value? = computeProperty(name, descriptor?.get(name))
override fun setValue(name: Name, value: Value?) {
setProperty(name, value)
}
}

View File

@ -9,7 +9,7 @@ import kotlin.properties.ReadOnlyProperty
/**
* A reference to a style defined in a specific container
*/
public class StyleReference(public val owner: VisionGroup, public val name: String)
public class StyleReference(public val owner: Vision, public val name: String)
private tailrec fun styleIsDefined(vision: Vision, reference: StyleReference): Boolean = when {
reference.owner === vision -> true
@ -25,7 +25,7 @@ public fun Vision.useStyle(reference: StyleReference) {
}
@VisionBuilder
public fun VisionGroup.style(
public fun Vision.style(
styleKey: String? = null,
builder: MutableMeta.() -> Unit,
): ReadOnlyProperty<Any?, StyleReference> = ReadOnlyProperty { _, property ->
@ -35,7 +35,7 @@ public fun VisionGroup.style(
}
@VisionBuilder
public fun <T : Scheme> VisionGroup.style(
public fun <T : Scheme> Vision.style(
spec: Specification<T>,
styleKey: String? = null,
builder: T.() -> Unit,

View File

@ -5,7 +5,6 @@ import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.plus
import space.kscience.dataforge.values.Value
import space.kscience.dataforge.values.asValue
import space.kscience.dataforge.values.stringList
import kotlin.jvm.JvmInline
@ -14,11 +13,11 @@ import kotlin.jvm.JvmInline
* A container for styles
*/
@JvmInline
public value class StyleSheet(private val owner: VisionGroup) {
public value class StyleSheet(private val owner: Vision) {
private val styleNode: Meta? get() = owner.meta[STYLESHEET_KEY]
private val styleNode: Meta get() = owner.getProperty(STYLESHEET_KEY)
public val items: Map<NameToken, Meta>? get() = styleNode?.items
public val items: Map<NameToken, Meta> get() = styleNode.items
public operator fun get(key: String): Meta? = owner.getStyle(key)
@ -26,7 +25,7 @@ public value class StyleSheet(private val owner: VisionGroup) {
* Define a style without notifying owner
*/
public fun define(key: String, style: Meta?) {
owner.meta.setMeta(STYLESHEET_KEY + key, style)
owner.setProperty(STYLESHEET_KEY + key, style)
}
/**
@ -43,7 +42,7 @@ public value class StyleSheet(private val owner: VisionGroup) {
/**
* Create and set a style
*/
public operator fun set(key: String, builder: MutableMeta.() -> Unit) {
public fun update(key: String, builder: MutableMeta.() -> Unit) {
val newStyle = get(key)?.toMutableMeta()?.apply(builder) ?: Meta(builder)
set(key, newStyle.seal())
}
@ -61,10 +60,8 @@ internal fun Vision.styleChanged(key: String, oldStyle: Meta?, newStyle: Meta?)
.map { it.asName() }
tokens.forEach { parent?.invalidateProperty(it) }
}
if (this is VisionGroup) {
for (obj in this) {
obj.styleChanged(key, oldStyle, newStyle)
}
children.values.forEach { vision ->
vision.styleChanged(key, oldStyle, newStyle)
}
}
@ -73,35 +70,40 @@ internal fun Vision.styleChanged(key: String, oldStyle: Meta?, newStyle: Meta?)
* List of names of styles applied to this object. Order matters. Not inherited.
*/
public var Vision.styles: List<String>
get() = meta.getValue(Vision.STYLE_KEY)?.stringList ?: emptyList()
get() = getPropertyValue(
Vision.STYLE_KEY,
inherit = true,
includeStyles = false,
includeDefaults = false
)?.stringList ?: emptyList()
set(value) {
meta.setValue(Vision.STYLE_KEY, value.map { it.asValue() }.asValue())
setPropertyValue(Vision.STYLE_KEY, value.map { it.asValue() }.asValue())
}
/**
* A stylesheet for this group and its descendants. Stylesheet is not applied directly,
* but instead is just a repository for named configurations.
*/
public val VisionGroup.styleSheet: StyleSheet get() = StyleSheet(this)
public val Vision.styleSheet: StyleSheet get() = StyleSheet(this)
/**
* Add style name to the list of styles to be resolved later. The style with given name does not necessary exist at the moment.
*/
public fun Vision.useStyle(name: String) {
styles = (meta.getMeta(Vision.STYLE_KEY)?.stringList ?: emptyList()) + name
styles = (getPropertyValue(Vision.STYLE_KEY)?.stringList ?: emptyList()) + name
}
/**
* Find a style with given name for given [Vision]. The style is not necessary applied to this [Vision].
* Resolve a style with given name for given [Vision]. The style is not necessarily applied to this [Vision].
*/
public tailrec fun Vision.getStyle(name: String): Meta? =
public fun Vision.getStyle(name: String): Meta? =
meta.getMeta(StyleSheet.STYLESHEET_KEY + name) ?: parent?.getStyle(name)
/**
* Resolve a property from all styles
*/
public fun Vision.getStyleProperty(name: Name): Value? = styles.firstNotNullOfOrNull { getStyle(it)?.get(name)?.value }
public fun Vision.getStyleProperty(name: Name): Meta? = styles.firstNotNullOfOrNull { getStyle(it)?.get(name) }
/**
* Resolve an item in all style layers

View File

@ -1,17 +1,20 @@
package space.kscience.visionforge
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.launch
import space.kscience.dataforge.meta.*
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import space.kscience.dataforge.context.Global
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MutableMeta
import space.kscience.dataforge.meta.MutableMetaProvider
import space.kscience.dataforge.meta.descriptors.Described
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.meta.descriptors.get
import space.kscience.dataforge.misc.Type
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.parseAsName
import space.kscience.dataforge.names.startsWith
import space.kscience.dataforge.values.Value
import space.kscience.dataforge.values.asValue
@ -23,38 +26,59 @@ import kotlin.reflect.KProperty1
* A root type for display hierarchy
*/
@Type(TYPE)
public interface Vision : Described, Configurable {
public interface Vision : Described {
/**
* The parent object of this one. If null, this one is a root.
*/
public var parent: VisionGroup?
public var parent: Vision?
/**
* Owner [VisionManager]. Used to define coroutine scope a serialization
*/
public val manager: VisionManager? get() = parent?.manager
public val manager: VisionManager get() = parent?.manager ?: Global.visionManager
public val children: VisionChildren
/**
* This Vision own properties (ignoring inheritance, styles and defaults)
* Own properties without inheritance or styles.
*/
override val meta: ObservableMutableMeta
public val meta: Meta
/**
* Get property value with given layer flags.
* @param inherit toggles parent node property lookup. Null means inference from descriptor. Default is false.
* @param includeStyles toggles inclusion of properties from styles. default is true
*/
public fun getPropertyValue(
name: Name,
inherit: Boolean = false,
includeStyles: Boolean = true,
includeDefaults: Boolean = true,
inherit: Boolean,
includeStyles: Boolean,
includeDefaults: Boolean,
): Value?
/**
* Get property with given layer flags.
* @param inherit toggles parent node property lookup. Null means inference from descriptor.
* @param includeStyles toggles inclusion of properties from styles.
*/
public fun getProperty(
name: Name,
inherit: Boolean,
includeStyles: Boolean,
includeDefaults: Boolean,
): MutableMeta = VisionProperties(this, name, descriptor?.get(name), inherit, includeStyles)
public fun setProperty(
name: Name,
node: Meta?,
)
public fun setPropertyValue(
name: Name,
value: Value?,
)
public val propertyChanges: SharedFlow<Name>
/**
* Notify all listeners that a property has been changed and should be invalidated
* Notify all listeners that a property has been changed and should be invalidated.
* This method does not check that the property has actually changed.
*/
public fun invalidateProperty(propertyName: Name)
@ -68,80 +92,135 @@ public interface Vision : Described, Configurable {
public companion object {
public const val TYPE: String = "vision"
public val STYLE_KEY: Name = "@style".asName()
public const val STYLE_TARGET: String = "style"
public val VISIBLE_KEY: Name = "visible".asName()
}
}
/**
* Flow of property invalidation events. It does not contain property values after invalidation since it is not clear
* if it should include inherited properties etc.
*/
@OptIn(ExperimentalCoroutinesApi::class)
@DFExperimental
public val Vision.propertyChanges: Flow<Name>
get() = callbackFlow {
meta.onChange(this) { name ->
launch {
send(name)
}
}
awaitClose {
meta.removeListener(this)
}
}
public fun Vision.getPropertyValue(
name: Name,
inherit: Boolean? = null,
includeStyles: Boolean? = null,
includeDefaults: Boolean = true,
metaDescriptor: MetaDescriptor? = descriptor?.get(name),
): Value? {
val inheritFlag = inherit ?: metaDescriptor?.inherited ?: false
val stylesFlag = includeStyles ?: metaDescriptor?.usesStyles ?: true
return getPropertyValue(name, inheritFlag, stylesFlag, includeDefaults)
}
public fun Vision.getPropertyValue(
name: String,
inherit: Boolean? = null,
includeStyles: Boolean? = null,
includeDefaults: Boolean = true,
metaDescriptor: MetaDescriptor? = descriptor?.get(name),
): Value? = getPropertyValue(name.parseAsName(), inherit, includeStyles, includeDefaults, metaDescriptor)
/**
* Subscribe on property updates. The subscription is bound to the given scope and canceled when the scope is canceled
* Compute the property based on the provided value descriptor. By default, use Vision own descriptor
*/
public fun Vision.onPropertyChange(callback: Meta.(Name) -> Unit) {
meta.onChange(null, callback)
public fun Vision.getProperty(
name: Name,
inherit: Boolean? = null,
includeStyles: Boolean? = null,
includeDefaults: Boolean = true,
metaDescriptor: MetaDescriptor? = descriptor?.get(name),
): MutableMeta {
val inheritFlag = inherit ?: metaDescriptor?.inherited ?: false
val stylesFlag = includeStyles ?: metaDescriptor?.usesStyles ?: true
return getProperty(name, inheritFlag, stylesFlag, includeDefaults)
}
/**
* Get [Vision] property using key as a String
*/
public fun Vision.getPropertyValue(
key: String,
inherit: Boolean = false,
includeStyles: Boolean = true,
public fun Vision.getProperty(
name: String,
inherit: Boolean? = null,
includeStyles: Boolean? = null,
includeDefaults: Boolean = true,
): Value? = getPropertyValue(Name.parse(key), inherit, includeStyles, includeDefaults)
metaDescriptor: MetaDescriptor? = descriptor?.get(name),
): MutableMeta = getProperty(name.parseAsName(), inherit, includeStyles, includeDefaults, metaDescriptor)
/**
* A convenience method to set property node or value. If Item is null, then node is removed, not a value
* Vision's own non-inheritable, non-styleable properties
*/
public fun Vision.setProperty(name: Name, item: Any?) {
when (item) {
null -> meta.remove(name)
is Meta -> meta.setMeta(name, item)
is Value -> meta.setValue(name, item)
else -> meta.setValue(name, Value.of(item))
public fun Vision.properties(
inherit: Boolean? = null,
useStyles: Boolean? = null,
): MutableMetaProvider = VisionProperties(this, Name.EMPTY, inherit = inherit, useStyles = useStyles)
public fun Vision.setPropertyValue(name: Name, value: Number?) {
if (value == null) {
setPropertyValue(name, null)
} else {
setPropertyValue(name, value.asValue())
}
}
public fun Vision.setPropertyNode(key: String, item: Any?) {
setProperty(Name.parse(key), item)
public fun Vision.setPropertyValue(name: String, value: Number?): Unit =
setPropertyValue(name.parseAsName(), value)
public fun Vision.setPropertyValue(name: Name, value: Boolean?) {
if (value == null) {
setPropertyValue(name, null)
} else {
setPropertyValue(name, value.asValue())
}
}
public fun Vision.setPropertyValue(name: String, value: Boolean?): Unit =
setPropertyValue(name.parseAsName(), value)
public fun Vision.setPropertyValue(name: Name, value: String?) {
if (value == null) {
setPropertyValue(name, null)
} else {
setPropertyValue(name, value.asValue())
}
}
public fun Vision.setPropertyValue(name: String, value: String?): Unit =
setPropertyValue(name.parseAsName(), value)
/**
* Control visibility of the element
*/
public var Vision.visible: Boolean?
get() = getPropertyValue(Vision.VISIBLE_KEY)?.boolean
set(value) = meta.setValue(Vision.VISIBLE_KEY, value?.asValue())
set(value) {
setPropertyValue(Vision.VISIBLE_KEY, value)
}
/**
* Subscribe on property updates. The subscription is bound to the given scope and canceled when the scope is canceled
*/
public fun Vision.onPropertyChange(callback: (Name) -> Unit): Job = propertyChanges.onEach {
callback(it)
}.launchIn(manager.context)
public fun <V : Vision, T> V.useProperty(
property: KProperty1<V, T>,
owner: Any? = null,
callBack: V.(T) -> Unit,
) {
): Job {
//Pass initial value.
callBack(property.get(this))
meta.onChange(owner) { name ->
return propertyChanges.onEach { name ->
if (name.startsWith(property.name.asName())) {
callBack(property.get(this@useProperty))
}
}
}
}.launchIn(manager.context)
}
public interface MutableVisionGroup : Vision {
override val children: MutableVisionChildren
public fun createGroup(): MutableVisionGroup
}

View File

@ -1,176 +0,0 @@
package space.kscience.visionforge
import kotlinx.serialization.EncodeDefault
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MutableMeta
import space.kscience.dataforge.meta.ObservableMutableMeta
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.value
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.*
import space.kscience.dataforge.values.Value
import space.kscience.dataforge.values.ValueType
import space.kscience.visionforge.Vision.Companion.STYLE_KEY
import kotlin.jvm.Synchronized
internal data class MetaListener(
val owner: Any? = null,
val callback: Meta.(name: Name) -> Unit,
)
/**
* A full base implementation for a [Vision]
* @param parent the parent object for this vision. Could've set later. Not serialized.
*/
@Serializable
@SerialName("vision")
public open class VisionBase(
@Transient override var parent: VisionGroup? = null,
@EncodeDefault protected var properties: MutableMeta? = null,
) : Vision {
@Synchronized
protected fun getOrCreateProperties(): MutableMeta {
if (properties == null) {
val newProperties = MutableMeta()
properties = newProperties
}
return properties!!
}
@Transient
private val listeners: MutableList<MetaListener> = mutableListOf()
private inner class VisionProperties(val pathName: Name) : ObservableMutableMeta {
override val items: Map<NameToken, ObservableMutableMeta>
get() = properties?.get(pathName)?.items?.mapValues { entry ->
VisionProperties(pathName + entry.key)
} ?: emptyMap()
override var value: Value?
get() = properties?.get(pathName)?.value
set(value) {
val oldValue = properties?.get(pathName)?.value
getOrCreateProperties().setValue(pathName, value)
if (oldValue != value) {
invalidate(Name.EMPTY)
}
}
override fun getOrCreate(name: Name): ObservableMutableMeta = VisionProperties(pathName + name)
override fun setMeta(name: Name, node: Meta?) {
getOrCreateProperties().setMeta(pathName + name, node)
invalidate(name)
}
@DFExperimental
override fun attach(name: Name, node: ObservableMutableMeta) {
val ownProperties = getOrCreateProperties()
if (ownProperties is ObservableMutableMeta) {
ownProperties.attach(pathName + name, node)
} else {
ownProperties.setMeta(pathName + name, node)
node.onChange(this) { childName ->
ownProperties.setMeta(pathName + name + childName, this[childName])
}
}
}
override fun invalidate(name: Name) {
invalidateProperty(pathName + name)
}
@Synchronized
override fun onChange(owner: Any?, callback: Meta.(name: Name) -> Unit) {
if (pathName.isEmpty()) {
listeners.add((MetaListener(owner, callback)))
} else {
listeners.add(MetaListener(owner) { name ->
if (name.startsWith(pathName)) {
(this@MetaListener[pathName] ?: Meta.EMPTY).callback(name.removeHeadOrNull(pathName)!!)
}
})
}
}
@Synchronized
override fun removeListener(owner: Any?) {
listeners.removeAll { it.owner === owner }
}
override fun toString(): String = Meta.toString(this)
override fun equals(other: Any?): Boolean = Meta.equals(this, other as? Meta)
override fun hashCode(): Int = Meta.hashCode(this)
}
final override val meta: ObservableMutableMeta get() = VisionProperties(Name.EMPTY)
override fun getPropertyValue(
name: Name,
inherit: Boolean,
includeStyles: Boolean,
includeDefaults: Boolean,
): Value? {
properties?.get(name)?.value?.let { return it }
if (includeStyles) {
getStyleProperty(name)?.let { return it }
}
if (inherit) {
parent?.getPropertyValue(name, inherit, includeStyles, includeDefaults)?.let { return it }
}
if (includeDefaults) {
descriptor?.defaultNode?.get(name)?.value.let { return it }
}
return null
}
override val descriptor: MetaDescriptor? get() = null
override fun invalidateProperty(propertyName: Name) {
if (propertyName == STYLE_KEY) {
styles.mapNotNull { getStyle(it) }.asSequence()
.flatMap { it.items.asSequence() }
.distinctBy { it.key }
.forEach {
invalidateProperty(it.key.asName())
}
}
listeners.forEach { it.callback(properties ?: Meta.EMPTY, propertyName) }
}
override fun update(change: VisionChange) {
change.properties?.let {
updateProperties(Name.EMPTY, it)
}
}
public companion object {
public val descriptor: MetaDescriptor = MetaDescriptor {
value(STYLE_KEY, ValueType.STRING) {
multiple = true
}
}
public fun Vision.updateProperties(at: Name, item: Meta) {
meta.setValue(at, item.value)
item.items.forEach { (token, item) ->
updateProperties(at + token, item)
}
}
}
}
//fun VisualObject.findStyle(styleName: Name): Meta? {
// if (this is VisualGroup) {
// val style = resolveStyle(styleName)
// if (style != null) return style
// }
// return parent?.findStyle(styleName)
//}

View File

@ -7,7 +7,6 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.serialization.Serializable
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.plus
import space.kscience.dataforge.values.Null
@ -19,14 +18,13 @@ import kotlin.time.Duration
*/
private fun Vision.deepCopy(): Vision {
//Assuming that unrooted visions are already isolated
val manager = this.manager ?: return this
//TODO replace by efficient deep copy
val json = manager.encodeToJsonElement(this)
return manager.decodeFromJson(json)
}
/**
* An update for a [Vision] or a [VisionGroup]
* An update for a [Vision]
*/
public class VisionChangeBuilder : VisionContainerBuilder<Vision> {
@ -87,7 +85,6 @@ public inline fun VisionChange(block: VisionChangeBuilder.() -> Unit): VisionCha
VisionChangeBuilder().apply(block).deepCopy()
@OptIn(DFExperimental::class)
private fun CoroutineScope.collectChange(
name: Name,
source: Vision,
@ -96,28 +93,25 @@ private fun CoroutineScope.collectChange(
//Collect properties change
source.onPropertyChange { propertyName ->
val newItem = source.meta[propertyName]
val newItem = source.getProperty(propertyName, false, false, false)
collector().propertyChanged(name, propertyName, newItem)
}
if (source is VisionGroup) {
//Subscribe for children changes
source.children.forEach { (token, child) ->
collectChange(name + token, child, collector)
}
//Subscribe for structure change
if (source is MutableVisionGroup) {
source.structureChanges.onEach { changedName ->
val after = source[changedName]
val fullName = name + changedName
if (after != null) {
collectChange(fullName, after, collector)
}
collector()[fullName] = after
}.launchIn(this)
}
val children = source.children
//Subscribe for children changes
for ((token, child) in children) {
collectChange(name + token, child, collector)
}
//Subscribe for structure change
children.changes.onEach { changedName ->
val after = children[changedName]
val fullName = name + changedName
if (after != null) {
collectChange(fullName, after, collector)
}
collector()[fullName] = after
}.launchIn(this)
}
/**

View File

@ -0,0 +1,196 @@
package space.kscience.visionforge
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.serializer
import space.kscience.dataforge.names.*
@DslMarker
public annotation class VisionBuilder
public interface VisionContainer<out V : Vision> {
public operator fun get(name: Name): V?
}
public interface VisionContainerBuilder<in V : Vision> {
//TODO add documentation
public operator fun set(name: Name?, child: V?)
}
/**
* A serializable representation of [Vision] children container
*/
public interface VisionChildren : VisionContainer<Vision> {
public val parent: Vision?
public val keys: Set<NameToken>
public val values: Iterable<Vision> get() = keys.map { get(it)!! }
public val changes: Flow<Name>
public operator fun get(token: NameToken): Vision?
override fun get(name: Name): Vision? = when (name.length) {
0 -> parent
1 -> get(name.first())
else -> get(name.first())?.children?.get(name.cutFirst())
}
public companion object {
public fun empty(owner: Vision): VisionChildren = object : VisionChildren {
override val parent: Vision get() = owner
override val keys: Set<NameToken> get() = emptySet()
override val changes: Flow<Name> get() = emptyFlow()
override fun get(token: NameToken): Vision? = null
}
}
}
public fun VisionChildren.isEmpty(): Boolean = keys.isEmpty()
@Serializable(VisionChildrenContainerSerializer::class)
public interface MutableVisionChildren : VisionChildren, VisionContainerBuilder<Vision> {
public override val parent: MutableVisionGroup?
public operator fun set(token: NameToken, value: Vision?)
override fun set(name: Name?, child: Vision?) {
when {
name == null -> {
if (child != null) {
static(child)
}
}
name.isEmpty() -> error("Empty names are not allowed in VisionGroup::set")
name.length == 1 -> {
val token = name.tokens.first()
set(token, child)
}
else -> {
val currentParent = get(name.first())
if (currentParent != null && currentParent !is MutableVisionGroup) error("Can't assign a child to $currentParent")
val parent: MutableVisionGroup = currentParent as? MutableVisionGroup ?: parent?.createGroup().also {
set(name.first(), it)
} ?: error("Container owner not set")
parent.children[name.cutFirst()] = child
}
}
}
public fun clear()
}
/**
* Add a static child. Statics could not be found by name, removed or replaced. Changing statics also do not trigger events.
*/
public fun MutableVisionChildren.static(child: Vision): Unit {
set(NameToken("@static", index = child.hashCode().toString()), child)
}
public fun VisionChildren.asSequence(): Sequence<Pair<NameToken, Vision>> = sequence {
keys.forEach { yield(it to get(it)!!) }
}
public operator fun VisionChildren.iterator(): Iterator<Pair<NameToken, Vision>> = asSequence().iterator()
public operator fun <V : Vision> VisionContainer<V>.get(str: String): V? = get(Name.parse(str))
public operator fun <V : Vision> VisionContainerBuilder<V>.set(
str: String?, vision: V?,
): Unit = set(str?.parseAsName(), vision)
internal class VisionChildrenImpl(
items: Map<NameToken, Vision>,
) : MutableVisionChildren {
override var parent: MutableVisionGroup? = null
internal set
private val items = LinkedHashMap(items)
private val updateJobs = HashMap<NameToken, Job>()
private val scope: CoroutineScope? get() = parent?.manager?.context
override val keys: Set<NameToken> get() = items.keys
override fun get(token: NameToken): Vision? = items[token]
private val _changes = MutableSharedFlow<Name>()
override val changes: SharedFlow<Name> get() = _changes
private fun onChange(name: Name) {
scope?.launch {
_changes.emit(name)
}
}
override operator fun set(token: NameToken, value: Vision?) {
//fast return if value equals existing
if (value == get(token)) return
val currentUpdateJob = updateJobs[token]
if (currentUpdateJob != null) {
currentUpdateJob.cancel()
updateJobs.remove(token)
}
if (value == null) {
items.remove(token)
} else {
items[token] = value
//check if parent already exists and is different from the current one
if (value.parent != null && value.parent != parent) error("Can't reassign parent Vision for $value")
//set parent
value.parent = parent
//start update jobs (only if the vision is rooted)
scope?.let { scope ->
val job = (value.children as? VisionChildrenImpl)?.changes?.onEach {
onChange(token + it)
}?.launchIn(scope)
if (job != null) {
updateJobs[token] = job
}
}
}
onChange(token.asName())
}
override fun clear() {
if (items.isNotEmpty()) {
updateJobs.values.forEach {
it.cancel()
}
updateJobs.clear()
items.clear()
onChange(Name.EMPTY)
}
}
}
internal object VisionChildrenContainerSerializer : KSerializer<MutableVisionChildren> {
private val mapSerializer = serializer<Map<NameToken,Vision>>()
override val descriptor: SerialDescriptor = mapSerializer.descriptor
override fun deserialize(decoder: Decoder): MutableVisionChildren {
val map = decoder.decodeSerializableValue(mapSerializer)
return VisionChildrenImpl(map)
}
override fun serialize(encoder: Encoder, value: MutableVisionChildren) {
val map = value.keys.associateWith { value[it]!! }
encoder.encodeSerializableValue(mapSerializer, map)
}
}

View File

@ -1,109 +1,99 @@
package space.kscience.visionforge
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.launch
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.*
import space.kscience.dataforge.provider.Provider
@DslMarker
public annotation class VisionBuilder
public interface VisionContainer<out V : Vision> {
public operator fun get(name: Name): V?
}
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.value
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.plus
import space.kscience.dataforge.values.ValueType
import space.kscience.visionforge.Vision.Companion.STYLE_KEY
import kotlin.jvm.Synchronized
/**
* Represents a group of [Vision] instances
* A full base implementation for a [Vision]
*/
public interface VisionGroup : Provider, Vision, VisionContainer<Vision> {
/**
* A map of top level named children
*/
public val children: Map<NameToken, Vision>
@Serializable
@SerialName("vision.group")
public open class VisionGroup : AbstractVision(), MutableVisionGroup {
override val defaultTarget: String get() = Vision.TYPE
/**
* A map of direct children for specific target
* (currently "visual" or "style")
*/
override fun content(target: String): Map<Name, Any> =
when (target) {
Vision.TYPE -> children.flatMap { (key, value) ->
val res: Map<Name, Any> = if (value is VisionGroup) {
value.content(target).mapKeys { key + it.key }
} else {
mapOf(key.asName() to value)
}
res.entries
}.associate { it.toPair() }
STYLE_TARGET -> styleSheet.items?.mapKeys { it.key.asName() } ?: emptyMap()
else -> emptyMap()
}
public override operator fun get(name: Name): Vision? {
return when {
name.isEmpty() -> this
name.length == 1 -> children[name.tokens.first()]
else -> (children[name.tokens.first()] as? VisionGroup)?.get(name.cutFirst())
}
}
public companion object {
public const val STYLE_TARGET: String = "style"
}
}
/**
* Iterate over children of this group
*/
public operator fun VisionGroup.iterator(): Iterator<Vision> = children.values.iterator()
public fun VisionGroup.isEmpty(): Boolean = this.children.isEmpty()
public interface VisionContainerBuilder<in V : Vision> {
//TODO add documentation
public operator fun set(name: Name?, child: V?)
}
/**
* Mutable version of [VisionGroup]
*/
public interface MutableVisionGroup : VisionGroup, VisionContainerBuilder<Vision> {
public fun onStructureChanged(owner: Any?, block: VisionGroup.(Name) -> Unit)
public fun removeStructureListener(owner: Any?)
}
/**
* Flow structure changes of this group. Unconsumed changes are discarded
*/
@OptIn(ExperimentalCoroutinesApi::class)
@DFExperimental
public val MutableVisionGroup.structureChanges: Flow<Name>
get() = callbackFlow {
meta.onChange(this) { name ->
launch {
send(name)
override fun update(change: VisionChange) {
change.children?.forEach { (name, change) ->
when {
change.delete -> children.set(name, null)
change.vision != null -> children.set(name, change.vision)
else -> children[name]?.update(change)
}
}
awaitClose {
removeStructureListener(this)
change.properties?.let {
updateProperties(it, Name.EMPTY)
}
}
@SerialName("children")
protected var _children: MutableVisionChildren? = null
public operator fun <V : Vision> VisionContainer<V>.get(str: String): V? = get(Name.parse(str))
@Transient
override val children: MutableVisionChildren = object : MutableVisionChildren {
public operator fun <V : Vision> VisionContainerBuilder<V>.set(token: NameToken, child: V?): Unit =
set(token.asName(), child)
@Synchronized
fun getOrCreateChildren(): MutableVisionChildren {
if (_children == null) {
_children = VisionChildrenImpl(emptyMap()).apply {
parent = this@VisionGroup
}
}
return _children!!
}
public operator fun <V : Vision> VisionContainerBuilder<V>.set(key: String?, child: V?): Unit =
set(key?.let(Name::parse), child)
override val parent: MutableVisionGroup get() = this@VisionGroup
public fun MutableVisionGroup.removeAll(): Unit = children.keys.map { it.asName() }.forEach { this[it] = null }
override val keys: Set<NameToken> get() = _children?.keys ?: emptySet()
override val changes: Flow<Name> get() = _children?.changes ?: emptyFlow()
override fun get(token: NameToken): Vision? = _children?.get(token)
override fun set(token: NameToken, value: Vision?) {
getOrCreateChildren()[token] = value
}
override fun set(name: Name?, child: Vision?) {
getOrCreateChildren()[name] = child
}
override fun clear() {
_children?.clear()
}
}
override fun createGroup(): VisionGroup = VisionGroup()
public companion object {
public val descriptor: MetaDescriptor = MetaDescriptor {
value(STYLE_KEY, ValueType.STRING) {
multiple = true
}
}
public fun Vision.updateProperties(item: Meta, at: Name = Name.EMPTY) {
setPropertyValue(at, item.value)
item.items.forEach { (token, item) ->
updateProperties(item, at + token)
}
}
}
}
//fun VisualObject.findStyle(styleName: Name): Meta? {
// if (this is VisualGroup) {
// val style = resolveStyle(styleName)
// if (style != null) return style
// }
// return parent?.findStyle(styleName)
//}

View File

@ -1,168 +0,0 @@
package space.kscience.visionforge
import kotlinx.serialization.EncodeDefault
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import space.kscience.dataforge.names.*
import kotlin.jvm.Synchronized
private class StructureChangeListener(val owner: Any?, val callback: VisionGroup.(Name) -> Unit)
/**
* Abstract implementation of mutable group of [Vision]
*
* @param childrenInternal Internal mutable container for group children
*/
@Serializable
@SerialName("vision.group")
public open class VisionGroupBase(
@EncodeDefault @SerialName("children") protected val childrenInternal: MutableMap<NameToken, Vision> = LinkedHashMap(),
) : VisionBase(), MutableVisionGroup {
/**
* A map of top level named children
*/
override val children: Map<NameToken, Vision> get() = childrenInternal
init {
childrenInternal.forEach { (token, child) ->
if (child.parent != null && child.parent != this) error("Can't reassign existing parent for child $token")
child.parent = this
}
}
override fun invalidateProperty(propertyName: Name) {
super.invalidateProperty(propertyName)
for (obj in this) {
obj.invalidateProperty(propertyName)
}
}
@Transient
private val structureListeners = HashSet<StructureChangeListener>()
@Synchronized
override fun onStructureChanged(owner: Any?, block: VisionGroup.(Name) -> Unit) {
structureListeners.add(StructureChangeListener(owner, block))
}
@Synchronized
override fun removeStructureListener(owner: Any?) {
structureListeners.removeAll { it.owner == owner }
}
/**
* Propagate children change event upwards
*/
protected fun childrenChanged(name: Name) {
structureListeners.forEach {
it.callback(this, name)
}
}
/**
* 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 {
attachChild(NameToken("@static", index = child.hashCode().toString()), child)
}
/**
* Create a vision group of the same type as this vision group. Do not attach it.
*/
protected open fun createGroup(): VisionGroupBase = VisionGroupBase()
/**
* Set parent for given child and attach it
*/
private fun attachChild(token: NameToken, child: Vision?) {
val before = childrenInternal[token]
when {
child == null -> {
childrenInternal.remove(token)
}
child.parent == null -> {
child.parent = this
childrenInternal[token] = child
}
child.parent !== this -> {
error("Can't reassign existing parent for child $token")
}
}
if (before != child) {
childrenChanged(token.asName())
if (child is MutableVisionGroup) {
child.onStructureChanged(this) { changedName ->
this@VisionGroupBase.childrenChanged(token + changedName)
}
}
}
}
/**
* Recursively create a child group
*/
private fun createGroups(name: Name): VisionGroupBase = when {
name.isEmpty() -> error("Should be unreachable")
name.length == 1 -> {
val token = name.tokens.first()
when (val current = children[token]) {
null -> createGroup().also { child ->
attachChild(token, child)
}
is VisionGroupBase -> current
else -> error("Can't create group with name $name because it exists and not a group")
}
}
else -> createGroups(name.tokens.first().asName()).createGroups(name.cutFirst())
}
/**
* Add named or unnamed child to the group. If key is null the child is considered unnamed. Both key and value are not
* allowed to be null in the same time. If name is present and [child] is null, the appropriate element is removed.
*/
override fun set(name: Name?, child: Vision?): Unit {
when {
name == null -> {
if (child != null) {
addStatic(child)
}
}
name.isEmpty() -> error("Empty names are not allowed in VisionGroup::set")
name.length == 1 -> {
val token = name.tokens.first()
attachChild(token, child)
}
else -> {
//TODO add safety check
val parent = (get(name.cutLast()) as? MutableVisionGroup) ?: createGroups(name.cutLast())
parent[name.tokens.last().asName()] = child
}
}
}
override fun update(change: VisionChange) {
change.children?.forEach { (name, change) ->
when {
change.delete -> set(name, null)
change.vision != null -> set(name, change.vision)
else -> get(name)?.update(change)
}
}
super.update(change)
}
}
/**
* Non-serializable root group used to propagate manager to its children
*/
internal class RootVisionGroup(override val manager: VisionManager) : VisionGroupBase()
/**
* Designate this [VisionGroup] as a root and assign a [VisionManager] as its parent
*/
public fun Vision.setAsRoot(manager: VisionManager) {
if (parent != null) error("Vision $this already has a parent. It could not be set as root")
parent = RootVisionGroup(manager)
}

View File

@ -25,13 +25,14 @@ public class VisionManager(meta: Meta) : AbstractPlugin(meta) {
/**
* Combined [SerializersModule] for all registered visions
*/
public val serializersModule: SerializersModule
get() = SerializersModule {
public val serializersModule: SerializersModule by lazy {
SerializersModule {
include(defaultSerialModule)
context.gather<SerializersModule>(VISION_SERIALIZER_MODULE_TARGET).values.forEach {
include(it)
}
}
}
public val jsonFormat: Json
get() = Json(defaultJson) {
@ -67,9 +68,8 @@ public class VisionManager(meta: Meta) : AbstractPlugin(meta) {
private val defaultSerialModule: SerializersModule = SerializersModule {
polymorphic(Vision::class) {
default { VisionBase.serializer() }
subclass(VisionBase.serializer())
subclass(VisionGroupBase.serializer())
default { VisionGroup.serializer() }
subclass(VisionGroup.serializer())
subclass(VisionOfNumberField.serializer())
subclass(VisionOfTextField.serializer())
subclass(VisionOfCheckbox.serializer())
@ -107,5 +107,17 @@ public abstract class VisionPlugin(meta: Meta = Meta.EMPTY) : AbstractPlugin(met
*/
public val Context.visionManager: VisionManager get() = fetch(VisionManager)
public fun Vision.encodeToString(): String =
manager?.encodeToString(this) ?: error("VisionManager not defined in Vision")
public fun Vision.encodeToString(): String = manager.encodeToString(this)
/**
* A root vision attached to [VisionManager]
*/
public class RootVision(override val manager: VisionManager) : VisionGroup()
/**
* Designate this [Vision] as a root and assign a [VisionManager] as its parent
*/
public fun Vision.setAsRoot(manager: VisionManager) {
if (parent != null) error("Vision $this already has a parent. It could not be set as root")
parent = RootVision(manager)
}

View File

@ -0,0 +1,81 @@
package space.kscience.visionforge
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MutableMeta
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.get
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.plus
import space.kscience.dataforge.values.Value
/**
* A wrapper that emulates delegates reading and writing properties to Vision method
*/
internal class VisionProperties(
val vision: Vision,
val nodeName: Name,
val visionDescriptor: MetaDescriptor? = vision.descriptor,
val inherit: Boolean? = null,
val useStyles: Boolean? = null,
) : MutableMeta {
val descriptor: MetaDescriptor? by lazy { visionDescriptor?.get(nodeName) }
override val items: Map<NameToken, MutableMeta>
get() {
val metaKeys = vision.meta.getMeta(nodeName)?.items?.keys ?: emptySet()
val descriptorKeys = descriptor?.children?.map { NameToken(it.key) } ?: emptySet()
val inheritFlag = descriptor?.inherited ?: inherit
val stylesFlag = descriptor?.usesStyles ?: useStyles
return (metaKeys + descriptorKeys).associateWith {
VisionProperties(
vision,
nodeName + it,
visionDescriptor,
inheritFlag,
stylesFlag
)
}
}
override var value: Value?
get() {
val inheritFlag = descriptor?.inherited ?: inherit ?: false
val stylesFlag = descriptor?.usesStyles ?: useStyles ?: true
return vision.getPropertyValue(nodeName, inheritFlag, stylesFlag, true)
}
set(value) {
vision.setPropertyValue(nodeName, value)
}
override fun getOrCreate(name: Name): MutableMeta = VisionProperties(
vision,
nodeName + name,
visionDescriptor,
inherit,
useStyles
)
override fun setMeta(name: Name, node: Meta?) {
vision.setProperty(nodeName + name, node)
}
override fun toString(): String = Meta.toString(this)
override fun equals(other: Any?): Boolean = Meta.equals(this, other as? Meta)
override fun hashCode(): Int = Meta.hashCode(this)
}
///**
// * Accessor to all vision properties
// */
//public fun Vision.computePropertyValues(
// descriptor: MetaDescriptor? = this.descriptor,
//): MutableValueProvider = object : MutableValueProvider {
// override fun getValue(name: Name): Value? = computeProperty(name, descriptor?.get(name))?.value
//
// override fun setValue(name: Name, value: Value?) {
// setProperty(name, value)
// }
//}

View File

@ -1,34 +1,29 @@
package space.kscience.visionforge
import space.kscience.dataforge.meta.Configurable
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MutableMeta
import space.kscience.dataforge.meta.ObservableMutableMeta
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.values.Value
/**
* Property containers are used to create a symmetric behaviors for vision properties and style builders
*/
public interface VisionPropertyContainer<out V : Vision> {
public val meta: MutableMeta
public fun getPropertyValue(
name: Name,
inherit: Boolean = false,
includeStyles: Boolean = true,
includeDefaults: Boolean = true,
): Value?
}
public open class SimpleVisionPropertyContainer<out V : Vision>(
override val meta: ObservableMutableMeta,
) : VisionPropertyContainer<V>, Configurable {
override fun getPropertyValue(
public fun getProperty(
name: Name,
inherit: Boolean,
includeStyles: Boolean,
includeDefaults: Boolean
): Value? = meta[name]?.value
includeDefaults: Boolean,
): Meta?
}
public open class SimpleVisionPropertyContainer<out V : Vision>(
public val meta: MutableMeta,
) : VisionPropertyContainer<V> {
override fun getProperty(
name: Name,
inherit: Boolean,
includeStyles: Boolean,
includeDefaults: Boolean,
): Meta? = meta.getMeta(name)
}

View File

@ -9,13 +9,14 @@ import kotlinx.serialization.Serializable
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.node
import space.kscience.visionforge.properties
@Serializable
@SerialName("html.form")
public class VisionOfHtmlForm(
public val formId: String,
) : VisionOfHtmlInput() {
public var values: Meta? by meta.node()
public var values: Meta? by properties().node()
}
public class HtmlFormFragment internal constructor(

View File

@ -5,11 +5,12 @@ import kotlinx.serialization.Serializable
import space.kscience.dataforge.meta.boolean
import space.kscience.dataforge.meta.number
import space.kscience.dataforge.meta.string
import space.kscience.visionforge.VisionBase
import space.kscience.visionforge.VisionGroup
import space.kscience.visionforge.properties
@Serializable
public abstract class VisionOfHtmlInput : VisionBase() {
public var disabled: Boolean by meta.boolean(false)
public abstract class VisionOfHtmlInput : VisionGroup() {
public var disabled: Boolean by properties().boolean(false)
}
@Serializable
@ -18,7 +19,7 @@ public class VisionOfTextField(
public val label: String? = null,
public val name: String? = null,
) : VisionOfHtmlInput() {
public var text: String? by meta.string()
public var text: String? by properties().string()
}
@Serializable
@ -27,7 +28,7 @@ public class VisionOfCheckbox(
public val label: String? = null,
public val name: String? = null,
) : VisionOfHtmlInput() {
public var checked: Boolean? by meta.boolean()
public var checked: Boolean? by properties().boolean()
}
@Serializable
@ -36,7 +37,7 @@ public class VisionOfNumberField(
public val label: String? = null,
public val name: String? = null,
) : VisionOfHtmlInput() {
public var value: Number? by meta.number()
public var value: Number? by properties().number()
}
@Serializable
@ -48,6 +49,6 @@ public class VisionOfRangeField(
public val label: String? = null,
public val name: String? = null,
) : VisionOfHtmlInput() {
public var value: Number? by meta.number()
public var value: Number? by properties().number()
}

View File

@ -49,7 +49,7 @@ public fun Vision.propertyValue(
getPropertyValue(name ?: Name.parse(property.name), inherit, includeStyles, includeDefaults)
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Value?) {
meta.setValue(name ?: Name.parse(property.name), value)
setPropertyValue(name ?: Name.parse(property.name), value)
}
}
@ -69,7 +69,7 @@ public fun <T> Vision.propertyValue(
).let(getter)
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
meta.setValue(name ?: Name.parse(property.name), value?.let(setter))
setPropertyValue(name ?: Name.parse(property.name), value?.let(setter))
}
}

View File

@ -3,6 +3,7 @@ package space.kscience.visionforge
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.descriptors.*
import space.kscience.dataforge.values.asValue
import space.kscience.dataforge.values.set
private const val INHERITED_DESCRIPTOR_ATTRIBUTE = "inherited"
private const val STYLE_DESCRIPTOR_ATTRIBUTE = "useStyles"

View File

@ -1,6 +1,5 @@
package space.kscience.visionforge.visitor
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
@ -11,7 +10,6 @@ import space.kscience.visionforge.Vision
import kotlin.reflect.KClass
@OptIn(ExperimentalCoroutinesApi::class)
public suspend fun <T> Vision.flowStatistics(statistics: (Name, Vision) -> T): Flow<T> = callbackFlow<T> {
val visitor = object : VisionVisitor {
override suspend fun visit(name: Name, vision: Vision){

View File

@ -6,7 +6,7 @@ import kotlinx.coroutines.launch
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.plus
import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionGroup
import space.kscience.visionforge.iterator
public interface VisionVisitor {
/**
@ -19,30 +19,30 @@ public interface VisionVisitor {
/**
* Rearrange children of given group
*/
public suspend fun visitChildren(name: Name, group: VisionGroup) {
public suspend fun visitChildren(name: Name, group: Vision) {
//Do nothing by default
}
public fun skip(name: Name, vision: Vision): Boolean = false
public companion object{
public companion object {
private fun CoroutineScope.visitTreeAsync(
visionVisitor: VisionVisitor,
name: Name,
vision: Vision
vision: Vision,
): Job = launch {
if (visionVisitor.skip(name, vision)) return@launch
visionVisitor.visit(name, vision)
if (vision is VisionGroup) {
visionVisitor.visitChildren(name, vision)
for ((token, child) in vision.children) {
visitTreeAsync(visionVisitor, name + token, child)
}
visionVisitor.visitChildren(name, vision)
for ((token, child) in vision.children) {
visitTreeAsync(visionVisitor, name + token, child)
}
}
/**
* Recursively visit this [Vision] and all children
*/

View File

@ -4,13 +4,9 @@ import kotlinx.html.*
import kotlinx.html.stream.createHTML
import space.kscience.dataforge.context.Global
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.configure
import space.kscience.dataforge.meta.set
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name
import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionBase
import space.kscience.visionforge.VisionManager
import space.kscience.visionforge.*
import kotlin.collections.set
import kotlin.test.Test
@ -36,7 +32,7 @@ fun FlowContent.renderVisionFragment(
@DFExperimental
class HtmlTagTest {
fun VisionOutput.base(block: VisionBase.() -> Unit) = VisionBase().apply(block)
fun VisionOutput.base(block: VisionGroup.() -> Unit) = VisionGroup().apply(block)
val fragment: HtmlVisionFragment = {
div {
@ -46,10 +42,8 @@ class HtmlTagTest {
"metaProperty" put 87
}
base {
configure {
set("myProp", 82)
set("otherProp", false)
}
setPropertyValue("myProp", 82)
setPropertyValue("otherProp", false)
}
}
}
@ -59,7 +53,7 @@ class HtmlTagTest {
div {
h2 { +"Properties" }
ul {
(vision as? VisionBase)?.meta?.items?.forEach {
vision.getProperty(Name.EMPTY).items.forEach {
li {
a { +it.key.toString() }
p { +it.value.toString() }

View File

@ -1,8 +1,16 @@
package space.kscience.visionforge.meta
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.Scheme
import space.kscience.dataforge.meta.SchemeSpec
import space.kscience.dataforge.meta.int
import space.kscience.dataforge.meta.updateWith
import space.kscience.dataforge.values.asValue
import space.kscience.visionforge.VisionBase
import space.kscience.dataforge.values.boolean
import space.kscience.dataforge.values.int
import space.kscience.visionforge.VisionGroup
import space.kscience.visionforge.getProperty
import space.kscience.visionforge.getPropertyValue
import space.kscience.visionforge.setPropertyValue
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
@ -10,22 +18,22 @@ import kotlin.test.assertNotEquals
class VisionPropertyTest {
@Test
fun testPropertyWrite(){
val vision = VisionBase()
vision.meta["fff"] = 2
vision.meta["fff.ddd"] = false
val vision = VisionGroup()
vision.setPropertyValue("fff", 2)
vision.setPropertyValue("fff.ddd", false)
assertEquals(2, vision.meta["fff"]?.int)
assertEquals(false, vision.meta["fff.ddd"]?.boolean)
assertEquals(2, vision.getPropertyValue("fff")?.int)
assertEquals(false, vision.getPropertyValue("fff.ddd")?.boolean)
}
@Test
fun testPropertyEdit(){
val vision = VisionBase()
vision.meta.getOrCreate("fff.ddd").apply {
val vision = VisionGroup()
vision.getProperty("fff.ddd").apply {
value = 2.asValue()
}
assertEquals(2, vision.meta["fff.ddd"]?.int)
assertNotEquals(true, vision.meta["fff.ddd"]?.boolean)
assertEquals(2, vision.getPropertyValue("fff.ddd")?.int)
assertNotEquals(true, vision.getPropertyValue("fff.ddd")?.boolean)
}
internal class TestScheme: Scheme(){
@ -35,10 +43,10 @@ class VisionPropertyTest {
@Test
fun testPropertyUpdate(){
val vision = VisionBase()
vision.meta.getOrCreate("fff").updateWith(TestScheme){
val vision = VisionGroup()
vision.getProperty("fff").updateWith(TestScheme){
ddd = 2
}
assertEquals(2, vision.meta["fff.ddd"]?.int)
assertEquals(2, vision.getPropertyValue("fff.ddd")?.int)
}
}

View File

@ -6,11 +6,10 @@ import javafx.scene.Node
import javafx.scene.Parent
import javafx.scene.layout.VBox
import space.kscience.dataforge.meta.MutableMeta
import space.kscience.dataforge.meta.ObservableMutableMeta
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.names.Name
import space.kscience.visionforge.Vision
import space.kscience.visionforge.computeProperties
import space.kscience.visionforge.getProperty
import space.kscience.visionforge.getStyle
import space.kscience.visionforge.styles
import tornadofx.*
@ -21,8 +20,8 @@ public class VisionEditorFragment : Fragment() {
public var vision: Vision? by visionProperty
public val descriptorProperty: SimpleObjectProperty<MetaDescriptor> = SimpleObjectProperty<MetaDescriptor>()
private val configProperty: Binding<ObservableMutableMeta?> = visionProperty.objectBinding { vision ->
vision?.meta
private val configProperty: Binding<MutableMeta?> = visionProperty.objectBinding { vision ->
vision?.getProperty(Name.EMPTY)
}
private val configEditorProperty: Binding<Node?> = configProperty.objectBinding(descriptorProperty) {
@ -30,7 +29,7 @@ public class VisionEditorFragment : Fragment() {
val node:FXMetaModel<MutableMeta> = FXMetaModel(
meta,
vision?.descriptor,
vision?.computeProperties(),
vision?.meta,
Name.EMPTY,
"Vision properties"
)

View File

@ -5,17 +5,17 @@ import javafx.scene.control.SelectionMode
import javafx.scene.control.TreeItem
import javafx.scene.layout.VBox
import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionGroup
import space.kscience.visionforge.solid.SolidGroup
import tornadofx.*
private fun toTreeItem(vision: Vision, title: String): TreeItem<Pair<String, Vision>> {
return object : TreeItem<Pair<String, Vision>>(title to vision) {
init {
if (vision is VisionGroup) {
if (vision is SolidGroup) {
//lazy populate the tree
expandedProperty().onChange { expanded ->
if (expanded && children.isEmpty()) {
children.setAll(vision.children.map {
children.setAll(vision.items.map {
toTreeItem(it.value, it.key.toString())
})
}
@ -24,7 +24,7 @@ private fun toTreeItem(vision: Vision, title: String): TreeItem<Pair<String, Vis
}
override fun isLeaf(): Boolean {
return !(vision is VisionGroup && vision.children.isNotEmpty())
return !(vision is SolidGroup && vision.items.isNotEmpty())
}
}
}

View File

@ -16,7 +16,7 @@ import space.kscience.dataforge.context.*
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.boolean
import space.kscience.dataforge.misc.Type
import space.kscience.visionforge.computePropertyNode
import space.kscience.visionforge.getProperty
import space.kscience.visionforge.solid.FX3DFactory.Companion.TYPE
import space.kscience.visionforge.solid.SolidMaterial.Companion.MATERIAL_KEY
import space.kscience.visionforge.solid.SolidMaterial.Companion.MATERIAL_WIREFRAME_KEY
@ -48,9 +48,9 @@ public class FX3DPlugin : AbstractPlugin() {
public fun buildNode(obj: Solid): Node {
val binding = VisualObjectFXBinding(this, obj)
return when (obj) {
is SolidReferenceGroup -> referenceFactory(obj, binding)
is SolidReference -> referenceFactory(obj, binding)
is SolidGroup -> {
Group(obj.children.mapNotNull { (token, obj) ->
Group(obj.items.mapNotNull { (token, obj) ->
(obj as? Solid)?.let {
logger.info { token.toString() }
buildNode(it).apply {
@ -77,7 +77,7 @@ public class FX3DPlugin : AbstractPlugin() {
is PolyLine -> PolyLine3D(
obj.points.map { Point3D(it.x, it.y, it.z) },
obj.thickness.toFloat(),
obj.computePropertyNode(SolidMaterial.MATERIAL_COLOR_KEY)?.color()
obj.getProperty(SolidMaterial.MATERIAL_COLOR_KEY).color()
).apply {
this.meshView.cullFace = CullFace.FRONT
}

View File

@ -8,20 +8,21 @@ import space.kscience.dataforge.names.firstOrNull
import space.kscience.dataforge.names.isEmpty
import space.kscience.visionforge.Vision
import space.kscience.visionforge.onPropertyChange
import space.kscience.visionforge.solid.SolidReference.Companion.REFERENCE_CHILD_PROPERTY_PREFIX
import kotlin.reflect.KClass
public class FXReferenceFactory(public val plugin: FX3DPlugin) : FX3DFactory<SolidReferenceGroup> {
override val type: KClass<in SolidReferenceGroup> get() = SolidReferenceGroup::class
public class FXReferenceFactory(public val plugin: FX3DPlugin) : FX3DFactory<SolidReference> {
override val type: KClass<in SolidReference> get() = SolidReference::class
override fun invoke(obj: SolidReferenceGroup, binding: VisualObjectFXBinding): Node {
override fun invoke(obj: SolidReference, binding: VisualObjectFXBinding): Node {
val prototype = obj.prototype
val node = plugin.buildNode(prototype)
obj.onPropertyChange { name->
if (name.firstOrNull()?.body == SolidReferenceGroup.REFERENCE_CHILD_PROPERTY_PREFIX) {
if (name.firstOrNull()?.body == REFERENCE_CHILD_PROPERTY_PREFIX) {
val childName = name.firstOrNull()?.index?.let(Name::parse) ?: error("Wrong syntax for reference child property: '$name'")
val propertyName = name.cutFirst()
val referenceChild = obj[childName] ?: error("Reference child with name '$childName' not found")
val referenceChild = obj.children[childName] ?: error("Reference child with name '$childName' not found")
val child = node.findChild(childName) ?: error("Object child with name '$childName' not found")
child.updateProperty(referenceChild, propertyName)
}

View File

@ -7,7 +7,7 @@ import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.startsWith
import space.kscience.dataforge.values.Value
import space.kscience.visionforge.Vision
import space.kscience.visionforge.computePropertyNode
import space.kscience.visionforge.getProperty
import space.kscience.visionforge.onPropertyChange
import tornadofx.*
@ -36,7 +36,7 @@ public class VisualObjectFXBinding(public val fx: FX3DPlugin, public val obj: Vi
public operator fun get(key: Name): ObjectBinding<Meta?> {
return bindings.getOrPut(key) {
object : ObjectBinding<Meta?>() {
override fun computeValue(): Meta? = obj.computePropertyNode(key)
override fun computeValue(): Meta = obj.getProperty(key)
}
}
}

View File

@ -6,7 +6,7 @@ import space.kscience.dataforge.names.Name
import space.kscience.gdml.*
import space.kscience.visionforge.solid.Solid
import space.kscience.visionforge.solid.SolidMaterial
import space.kscience.visionforge.solid.invoke
import space.kscience.visionforge.solid.set
import space.kscience.visionforge.useStyle
import kotlin.random.Random
@ -44,7 +44,7 @@ public class GdmlLoaderOptions {
* Configure paint for given solid with given [GdmlMaterial]
*/
public var configurePaint: SolidMaterial.(material: GdmlMaterial, solid: GdmlSolid) -> Unit =
{ material, _ -> color(randomColor(material)) }
{ material, _ -> color.set(randomColor(material)) }
private set
public fun paint(block: SolidMaterial.(material: GdmlMaterial, solid: GdmlSolid) -> Unit) {

View File

@ -30,10 +30,10 @@ private class GdmlLoader(val settings: GdmlLoaderOptions) {
private val proto = SolidGroup()
private val solids = proto.group(solidsName) {
setPropertyNode("edges.enabled", false)
setPropertyValue("edges.enabled", false)
}
private val referenceStore = HashMap<Name, MutableList<SolidReferenceGroup>>()
private val referenceStore = HashMap<Name, MutableList<SolidReference>>()
fun Solid.configureSolid(root: Gdml, parent: GdmlVolume, solid: GdmlSolid) {
val material = parent.materialref.resolve(root) ?: GdmlElement(parent.materialref.ref)
@ -44,7 +44,7 @@ private class GdmlLoader(val settings: GdmlLoaderOptions) {
}
}
private fun proxySolid(root: Gdml, group: SolidGroup, solid: GdmlSolid, name: String): SolidReferenceGroup {
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)
@ -59,7 +59,7 @@ private class GdmlLoader(val settings: GdmlLoaderOptions) {
group: SolidGroup,
physVolume: GdmlPhysVolume,
volume: GdmlGroup,
): SolidReferenceGroup {
): SolidReference {
val templateName = volumesName + volume.name.asName()
if (proto[templateName] == null) {
proto[templateName] = volume(root, volume)
@ -321,7 +321,7 @@ private class GdmlLoader(val settings: GdmlLoaderOptions) {
?: error("Volume with ref ${divisionVolume.volumeref.ref} could not be resolved")
//TODO add divisions
set(null, volume(root, volume))
children.static(volume(root, volume))
}
private fun volume(
@ -355,7 +355,7 @@ private class GdmlLoader(val settings: GdmlLoaderOptions) {
final.useStyle(rootStyle)
final.prototypes {
proto.children.forEach { (token, item) ->
proto.items.forEach { (token, item) ->
item.parent = null
set(token.asName(), item as? Solid)
}
@ -383,9 +383,9 @@ public fun Gdml.toVision(block: GdmlLoaderOptions.() -> Unit = {}): SolidGroup {
* Append Gdml node to the group
*/
public fun SolidGroup.gdml(gdml: Gdml, key: String? = null, transformer: GdmlLoaderOptions.() -> Unit = {}) {
val visual = gdml.toVision(transformer)
val vision = gdml.toVision(transformer)
//println(Visual3DPlugin.json.stringify(VisualGroup3D.serializer(), visual))
set(key, visual)
children[key] = vision
}
@VisionBuilder

View File

@ -6,10 +6,9 @@ import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.length
import space.kscience.dataforge.names.plus
import space.kscience.visionforge.VisionGroup
import space.kscience.visionforge.solid.Solid
import space.kscience.visionforge.solid.SolidGroup
import space.kscience.visionforge.solid.SolidReferenceGroup
import space.kscience.visionforge.solid.SolidReference
import space.kscience.visionforge.solid.layer
@ -23,15 +22,15 @@ private class VisionCounterTree(
var selfCount = 1
val children: Map<NameToken, VisionCounterTree> by lazy {
(vision as? VisionGroup)?.children?.mapValues { (key, vision) ->
if (vision is SolidReferenceGroup) {
prototypes.getOrPut(vision.refName) {
VisionCounterTree(vision.refName, vision.prototype, prototypes)
(vision as? SolidGroup)?.items?.mapValues { (key, vision) ->
if (vision is SolidReference) {
prototypes.getOrPut(vision.prototypeName) {
VisionCounterTree(vision.prototypeName, vision.prototype, prototypes)
}.apply {
selfCount += 1
}
} else {
VisionCounterTree(name + key, vision as Solid, prototypes)
VisionCounterTree(name + key, vision, prototypes)
}
} ?: emptyMap()
}
@ -51,10 +50,10 @@ private fun VisionCounterTree.topToBottom(): Sequence<VisionCounterTree> = seque
}
public fun SolidGroup.markLayers(thresholds: List<Int> = listOf(500, 1000, 20000, 50000)) {
val logger = manager?.context?.logger
val logger = manager.context.logger
val counterTree = VisionCounterTree(Name.EMPTY, this, hashMapOf())
val totalCount = counterTree.childrenCount
if (totalCount > thresholds.firstOrNull() ?: 0) {
if (totalCount > (thresholds.firstOrNull() ?: 0)) {
val allNodes = counterTree.topToBottom().distinct().toMutableList()
//println("tree construction finished")
allNodes.sortWith(

View File

@ -21,12 +21,12 @@ class TestCubes {
@Test
fun testCubesDirect() {
val vision = cubes.toVision()
val vision: SolidGroup = cubes.toVision()
// println(Solids.encodeToString(vision))
val smallBoxPrototype = vision.getPrototype(Name.parse("solids.smallBox")) as? Box
assertNotNull(smallBoxPrototype)
assertEquals(30.0, smallBoxPrototype.xSize.toDouble())
val smallBoxVision = vision["composite-111.smallBox"]?.unref as? Box
val smallBoxVision = vision.children["composite-111.smallBox"]?.unref as? Box
assertNotNull(smallBoxVision)
assertEquals(30.0, smallBoxVision.xSize.toDouble())
}
@ -55,7 +55,7 @@ class TestCubes {
assertNotNull(this.prototype)
}
if (this is SolidGroup) {
children.forEach {
items.forEach {
it.value.checkPrototypes()
}
}

View File

@ -9,17 +9,18 @@ import space.kscience.dataforge.meta.string
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionBase
import space.kscience.visionforge.VisionGroup
import space.kscience.visionforge.properties
@Serializable
@SerialName("vision.markup")
public class VisionOfMarkup(
public val format: String = COMMONMARK_FORMAT
) : VisionBase() {
) : VisionGroup() {
//TODO add templates
public var content: String? by meta.string(CONTENT_PROPERTY_KEY)
public var content: String? by properties().string(CONTENT_PROPERTY_KEY)
public companion object {
public val CONTENT_PROPERTY_KEY: Name = "content".asName()

View File

@ -2,21 +2,24 @@ package space.kscience.visionforge.plotly
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import space.kscience.dataforge.meta.asObservable
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name
import space.kscience.plotly.Plot
import space.kscience.plotly.Plotly
import space.kscience.visionforge.VisionBase
import space.kscience.visionforge.VisionGroup
import space.kscience.visionforge.getProperty
import space.kscience.visionforge.html.VisionOutput
@Serializable
@SerialName("vision.plotly")
public class VisionOfPlotly private constructor() : VisionBase() {
public class VisionOfPlotly private constructor() : VisionGroup() {
public constructor(plot: Plot) : this() {
properties = plot.meta
setProperty(Name.EMPTY, plot.meta)
}
public val plot: Plot get() = Plot(meta)
public val plot: Plot get() = Plot(getProperty(Name.EMPTY).asObservable())
}
public fun Plot.asVision(): VisionOfPlotly = VisionOfPlotly(this)

View File

@ -8,7 +8,7 @@ import io.ktor.server.engine.embeddedServer
import io.ktor.server.html.respondHtml
import io.ktor.server.http.content.resources
import io.ktor.server.http.content.static
import io.ktor.server.plugins.cors.CORS
import io.ktor.server.plugins.cors.routing.CORS
import io.ktor.server.response.respond
import io.ktor.server.response.respondText
import io.ktor.server.routing.*
@ -18,7 +18,6 @@ import io.ktor.server.websocket.webSocket
import io.ktor.websocket.Frame
import kotlinx.coroutines.channels.consumeEach
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

View File

@ -717,7 +717,7 @@ public final class space/kscience/visionforge/solid/SolidMaterialKt {
}
public abstract interface class space/kscience/visionforge/solid/SolidReference : space/kscience/visionforge/VisionGroup {
public fun getPropertyValue (Lspace/kscience/dataforge/names/Name;ZZZ)Lspace/kscience/dataforge/values/Value;
public fun getProperty (Lspace/kscience/dataforge/names/Name;ZZZ)Lspace/kscience/dataforge/values/Value;
public abstract fun getPrototype ()Lspace/kscience/visionforge/solid/Solid;
}
@ -728,7 +728,7 @@ public final class space/kscience/visionforge/solid/SolidReferenceGroup : space/
public fun <init> (Lspace/kscience/dataforge/names/Name;)V
public fun getChildren ()Ljava/util/Map;
public fun getDescriptor ()Lspace/kscience/dataforge/meta/descriptors/MetaDescriptor;
public fun getPropertyValue (Lspace/kscience/dataforge/names/Name;ZZZ)Lspace/kscience/dataforge/values/Value;
public fun getProperty (Lspace/kscience/dataforge/names/Name;ZZZ)Lspace/kscience/dataforge/values/Value;
public fun getPrototype ()Lspace/kscience/visionforge/solid/Solid;
public final fun getRefName ()Lspace/kscience/dataforge/names/Name;
public static final fun write$Self (Lspace/kscience/visionforge/solid/SolidReferenceGroup;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V

View File

@ -1,12 +1,13 @@
package space.kscience.visionforge.solid
import space.kscience.dataforge.meta.Configurable
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.plus
import space.kscience.dataforge.values.*
import space.kscience.visionforge.Colors
import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionBuilder
import space.kscience.visionforge.getProperty
import kotlin.properties.ReadOnlyProperty
@VisionBuilder
@ -27,8 +28,8 @@ public class ColorAccessor(
}
}
public fun Configurable.color(): ReadOnlyProperty<Configurable, ColorAccessor> = ReadOnlyProperty { _, property ->
ColorAccessor(meta, property.name.asName())
public fun Vision.color(): ReadOnlyProperty<Vision, ColorAccessor> = ReadOnlyProperty { _, property ->
ColorAccessor(getProperty(Name.EMPTY), property.name.asName())
}
public var ColorAccessor?.string: String?
@ -40,21 +41,21 @@ public var ColorAccessor?.string: String?
/**
* Set [webcolor](https://en.wikipedia.org/wiki/Web_colors) as string
*/
public operator fun ColorAccessor?.invoke(webColor: String) {
public fun ColorAccessor?.set(webColor: String) {
this?.value = webColor.asValue()
}
/**
* Set color as RGB integer
*/
public operator fun ColorAccessor?.invoke(rgb: Int) {
public fun ColorAccessor?.set(rgb: Int) {
this?.value = Colors.rgbToString(rgb).asValue()
}
/**
* Set color as RGB
*/
public operator fun ColorAccessor?.invoke(r: UByte, g: UByte, b: UByte) {
public fun ColorAccessor?.set(r: UByte, g: UByte, b: UByte) {
this?.value = Colors.rgbToString(r, g, b).asValue()
}

View File

@ -3,11 +3,8 @@ package space.kscience.visionforge.solid
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import space.kscience.dataforge.meta.isEmpty
import space.kscience.dataforge.meta.update
import space.kscience.visionforge.VisionBuilder
import space.kscience.visionforge.VisionContainerBuilder
import space.kscience.visionforge.VisionPropertyContainer
import space.kscience.visionforge.set
import space.kscience.dataforge.names.Name
import space.kscience.visionforge.*
public enum class CompositeType {
GROUP, // Dumb sum of meshes
@ -28,16 +25,16 @@ public class Composite(
public inline fun VisionContainerBuilder<Solid>.composite(
type: CompositeType,
name: String? = null,
builder: SolidGroup.() -> Unit,
@VisionBuilder builder: SolidGroup.() -> Unit,
): Composite {
val group = SolidGroup().apply(builder)
val children = group.children.values.filterIsInstance<Solid>()
if (children.size != 2){
val group = SolidGroup(builder)
val children = group.items.values.toList()
if (children.size != 2) {
error("Composite requires exactly two children, but found ${children.size}")
}
val res = Composite(type, children[0], children[1])
res.meta.update(group.meta)
res.setProperty(Name.EMPTY, group.getProperty(Name.EMPTY))
set(name, res)
return res
@ -50,34 +47,34 @@ public inline fun VisionContainerBuilder<Solid>.composite(
public fun SolidGroup.smartComposite(
type: CompositeType,
name: String? = null,
builder: SolidGroup.() -> Unit,
@VisionBuilder builder: SolidGroup.() -> Unit,
): Solid = if (type == CompositeType.GROUP) {
val group = SolidGroup(builder)
if (name == null && group.meta.isEmpty()) {
//append directly to group if no properties are defined
group.children.forEach { (_, value) ->
group.items.forEach { (_, value) ->
value.parent = null
set(null, value)
children.static(value)
}
this
} else {
set(name, group)
children[name] = group
group
}
} else {
composite(type, name, builder)
children.composite(type, name, builder)
}
@VisionBuilder
public inline fun VisionContainerBuilder<Solid>.union(
name: String? = null,
builder: SolidGroup.() -> Unit
builder: SolidGroup.() -> Unit,
): Composite = composite(CompositeType.UNION, name, builder = builder)
@VisionBuilder
public inline fun VisionContainerBuilder<Solid>.subtract(
name: String? = null,
builder: SolidGroup.() -> Unit
builder: SolidGroup.() -> Unit,
): Composite = composite(CompositeType.SUBTRACT, name, builder = builder)
@VisionBuilder

View File

@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import space.kscience.dataforge.meta.MutableMeta
import space.kscience.dataforge.meta.ObservableMutableMeta
import space.kscience.dataforge.meta.configure
import space.kscience.dataforge.names.Name
import space.kscience.visionforge.*
import kotlin.math.PI
import kotlin.math.cos
@ -40,7 +40,7 @@ public data class Layer(var x: Float, var y: Float, var z: Float, var scale: Flo
@SerialName("solid.extrude")
public class Extruded(
public val shape: List<Point2D>,
public val layers: List<Layer>
public val layers: List<Layer>,
) : SolidBase(), GeometrySolid, VisionPropertyContainer<Extruded> {
override fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>) {
@ -67,7 +67,7 @@ public class Extruded(
for (i in (1 until layers.size)) {
upperLayer = layers[i]
for (j in (0 until shape.size - 1)) {
//counter clockwise
//counterclockwise
geometryBuilder.face4(
lowerLayer[j],
lowerLayer[j + 1],
@ -99,7 +99,7 @@ public class ExtrudeBuilder(
public var layers: MutableList<Layer> = ArrayList(),
config: ObservableMutableMeta = MutableMeta()
config: ObservableMutableMeta = MutableMeta(),
) : SimpleVisionPropertyContainer<Extruded>(config) {
public fun shape(block: Shape2DBuilder.() -> Unit) {
this.shape = Shape2DBuilder().apply(block).build()
@ -110,12 +110,12 @@ public class ExtrudeBuilder(
}
internal fun build(): Extruded = Extruded(shape, layers).apply {
configure(this@ExtrudeBuilder.meta)
setProperty(Name.EMPTY, getProperty(Name.EMPTY))
}
}
@VisionBuilder
public fun VisionContainerBuilder<Solid>.extruded(
name: String? = null,
action: ExtrudeBuilder.() -> Unit = {}
action: ExtrudeBuilder.() -> Unit = {},
): Extruded = ExtrudeBuilder().apply(action).build().also { set(name, it) }

View File

@ -1,11 +0,0 @@
package space.kscience.visionforge.solid
import kotlin.jvm.JvmInline
@JvmInline
public value class Quaternion(public val values: DoubleArray)
public operator fun Quaternion.component1(): Double = values[0]
public operator fun Quaternion.component2(): Double = values[1]
public operator fun Quaternion.component3(): Double = values[2]
public operator fun Quaternion.component4(): Double = values[3]

View File

@ -6,16 +6,12 @@ import space.kscience.dataforge.meta.descriptors.node
import space.kscience.dataforge.meta.descriptors.value
import space.kscience.dataforge.meta.float
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.number
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.plus
import space.kscience.dataforge.values.*
import space.kscience.visionforge.Vision
import space.kscience.visionforge.*
import space.kscience.visionforge.Vision.Companion.VISIBLE_KEY
import space.kscience.visionforge.hide
import space.kscience.visionforge.inherited
import space.kscience.visionforge.setProperty
import space.kscience.visionforge.solid.Solid.Companion.DETAIL_KEY
import space.kscience.visionforge.solid.Solid.Companion.IGNORE_KEY
import space.kscience.visionforge.solid.Solid.Companion.LAYER_KEY
@ -38,7 +34,7 @@ import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
/**
* Interface for 3-dimensional [Vision]
* Interface for a [Vision] representing a 3D object
*/
public interface Solid : Vision {
@ -121,7 +117,7 @@ public interface Solid : Vision {
public var Solid.layer: Int
get() = getPropertyValue(LAYER_KEY, inherit = true)?.int ?: 0
set(value) {
setProperty(LAYER_KEY, value)
setPropertyValue(LAYER_KEY, value)
}
// Common properties
@ -140,23 +136,23 @@ public enum class RotationOrder {
*/
public var Solid.rotationOrder: RotationOrder
get() = getPropertyValue(Solid.ROTATION_ORDER_KEY)?.enum<RotationOrder>() ?: RotationOrder.XYZ
set(value) = meta.setValue(Solid.ROTATION_ORDER_KEY, value.name.asValue())
set(value) = setPropertyValue(Solid.ROTATION_ORDER_KEY, value.name.asValue())
/**
* Preferred number of polygons for displaying the object. If not defined, uses shape or renderer default. Not inherited
*/
public var Solid.detail: Int?
get() = getPropertyValue(DETAIL_KEY, false)?.int
set(value) = meta.setValue(DETAIL_KEY, value?.asValue())
get() = getPropertyValue(DETAIL_KEY, inherit = false)?.int
set(value) = setPropertyValue(DETAIL_KEY, value?.asValue())
/**
* If this property is true, the object will be ignored on render.
* Property is not inherited.
*/
public var Vision.ignore: Boolean?
get() = getPropertyValue(IGNORE_KEY, false)?.boolean
set(value) = meta.setValue(IGNORE_KEY, value?.asValue())
get() = getPropertyValue(IGNORE_KEY, inherit = false)?.boolean
set(value) = setPropertyValue(IGNORE_KEY, value?.asValue())
//var VisualObject.selected: Boolean?
// get() = getProperty(SELECTED_KEY).boolean
@ -165,18 +161,18 @@ public var Vision.ignore: Boolean?
internal fun float(name: Name, default: Number): ReadWriteProperty<Solid, Number> =
object : ReadWriteProperty<Solid, Number> {
override fun getValue(thisRef: Solid, property: KProperty<*>): Number {
return thisRef.meta.getMeta(name)?.number ?: default
return thisRef.getPropertyValue(name)?.number ?: default
}
override fun setValue(thisRef: Solid, property: KProperty<*>, value: Number) {
thisRef.setProperty(name, value)
thisRef.setPropertyValue(name, value)
}
}
internal fun point(name: Name, default: Float): ReadWriteProperty<Solid, Point3D?> =
object : ReadWriteProperty<Solid, Point3D?> {
override fun getValue(thisRef: Solid, property: KProperty<*>): Point3D? {
val item = thisRef.meta.getMeta(name) ?: return null
val item = thisRef.meta[name] ?: return null
return object : Point3D {
override val x: Float get() = item[X_KEY]?.float ?: default
override val y: Float get() = item[Y_KEY]?.float ?: default
@ -186,11 +182,11 @@ internal fun point(name: Name, default: Float): ReadWriteProperty<Solid, Point3D
override fun setValue(thisRef: Solid, property: KProperty<*>, value: Point3D?) {
if (value == null) {
thisRef.meta.setMeta(name, null)
thisRef.setProperty(name, null)
} else {
thisRef.setProperty(name + X_KEY, value.x)
thisRef.setProperty(name + Y_KEY, value.y)
thisRef.setProperty(name + Z_KEY, value.z)
thisRef.setPropertyValue(name + X_KEY, value.x)
thisRef.setPropertyValue(name + Y_KEY, value.y)
thisRef.setPropertyValue(name + Z_KEY, value.z)
}
}
}

View File

@ -2,11 +2,24 @@ package space.kscience.visionforge.solid
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import space.kscience.dataforge.meta.MutableMeta
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.visionforge.VisionBase
import space.kscience.dataforge.names.Name
import space.kscience.visionforge.AbstractVision
import space.kscience.visionforge.VisionChildren
@Serializable
@SerialName("solid")
public open class SolidBase : VisionBase(), Solid {
public open class SolidBase : AbstractVision(), Solid {
override val descriptor: MetaDescriptor get() = Solid.descriptor
override val children: VisionChildren get() = VisionChildren.empty(this)
override fun getProperty(
name: Name,
inherit: Boolean,
includeStyles: Boolean,
includeDefaults: Boolean,
): MutableMeta {
return super<AbstractVision>.getProperty(name, inherit, includeStyles, includeDefaults)
}
}

View File

@ -7,6 +7,7 @@ import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
import space.kscience.visionforge.*
/**
* A container with prototype support
*/
@ -23,20 +24,26 @@ public interface PrototypeHolder {
public fun getPrototype(name: Name): Solid?
}
/**
* Represents 3-dimensional Visual Group
* @param prototypes A container for templates visible inside this group
* A [Solid] group with additional accessor methods
*/
@Serializable
@SerialName("group.solid")
public class SolidGroup : VisionGroupBase(), Solid, PrototypeHolder {
public class SolidGroup : VisionGroup(), Solid, PrototypeHolder, MutableVisionGroup, VisionContainerBuilder<Solid> {
override val children: Map<NameToken, Vision> get() = super.childrenInternal.filter { it.key != PROTOTYPES_TOKEN }
public val items: Map<NameToken, Solid>
get() = children.keys.mapNotNull {
val value = children[it] as? Solid ?: return@mapNotNull null
it to value
}.toMap()
private var prototypes: MutableVisionGroup?
get() = childrenInternal[PROTOTYPES_TOKEN] as? MutableVisionGroup
public operator fun get(name: Name): Solid? = children[name] as? Solid
private var prototypes: SolidGroup?
get() = items[PROTOTYPES_TOKEN] as? SolidGroup
set(value) {
set(PROTOTYPES_TOKEN, value)
children[PROTOTYPES_TOKEN] = value
}
@ -53,36 +60,38 @@ public class SolidGroup : VisionGroupBase(), Solid, PrototypeHolder {
* Create or edit prototype node as a group
*/
override fun prototypes(builder: VisionContainerBuilder<Solid>.() -> Unit): Unit {
(prototypes ?: SolidGroup().also {
prototypes = it
}).run(builder)
(prototypes ?: SolidGroup().also { prototypes = it }).children.run(builder)
}
override fun createGroup(): SolidGroup = SolidGroup()
//
// override fun update(change: VisionChange) {
// updatePosition(change.properties)
// super.update(change)
// }
override fun set(name: Name?, child: Solid?) {
children[name] = child
}
public companion object {
public val PROTOTYPES_TOKEN: NameToken = NameToken("@prototypes")
}
}
@Suppress("FunctionName")
public fun SolidGroup(block: SolidGroup.() -> Unit): SolidGroup = SolidGroup().apply(block)
public inline fun SolidGroup(block: SolidGroup.() -> Unit): SolidGroup = SolidGroup().apply(block)
@VisionBuilder
public fun VisionContainerBuilder<Vision>.group(
public fun VisionContainerBuilder<Solid>.group(
name: Name? = null,
builder: SolidGroup.() -> Unit = {},
): SolidGroup = SolidGroup().apply(builder).also { set(name, it) }
): SolidGroup = SolidGroup(builder).also { set(name, it) }
/**
* Define a group with given [name], attach it to this parent and return it.
*/
@VisionBuilder
public fun VisionContainerBuilder<Vision>.group(name: String, action: SolidGroup.() -> Unit = {}): SolidGroup =
SolidGroup().apply(action).also { set(name, it) }
public fun VisionContainerBuilder<Solid>.group(
name: String,
action: SolidGroup.() -> Unit = {},
): SolidGroup = SolidGroup(action).also { set(name, it) }

View File

@ -9,6 +9,7 @@ import space.kscience.dataforge.names.plus
import space.kscience.dataforge.values.ValueType
import space.kscience.dataforge.values.asValue
import space.kscience.dataforge.values.number
import space.kscience.dataforge.values.set
import space.kscience.visionforge.*
import space.kscience.visionforge.solid.SolidMaterial.Companion.MATERIAL_COLOR_KEY
import space.kscience.visionforge.solid.SolidMaterial.Companion.MATERIAL_KEY
@ -101,19 +102,19 @@ public class SolidMaterial : Scheme() {
}
public val Solid.color: ColorAccessor
get() = ColorAccessor(computePropertyValues(), MATERIAL_COLOR_KEY)
get() = ColorAccessor(getProperty(Name.EMPTY), MATERIAL_COLOR_KEY)
public var Solid.material: SolidMaterial?
get() = computePropertyNode(MATERIAL_KEY)?.let { SolidMaterial.read(it) }
set(value) = meta.setMeta(MATERIAL_KEY, value?.meta)
get() = SolidMaterial.read(getProperty(MATERIAL_KEY))
set(value) = setProperty(MATERIAL_KEY, value?.meta)
@VisionBuilder
public fun Solid.material(builder: SolidMaterial.() -> Unit) {
meta.getOrCreate(MATERIAL_KEY).updateWith(SolidMaterial, builder)
getProperty(MATERIAL_KEY).updateWith(SolidMaterial, builder)
}
public var Solid.opacity: Number?
get() = getPropertyValue(MATERIAL_OPACITY_KEY, inherit = true)?.number
set(value) {
meta.setValue(MATERIAL_OPACITY_KEY, value?.asValue())
setPropertyValue(MATERIAL_OPACITY_KEY, value?.asValue())
}

View File

@ -1,38 +1,15 @@
package space.kscience.visionforge.solid
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import space.kscience.dataforge.meta.ObservableMutableMeta
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.names.*
import space.kscience.dataforge.values.Value
import space.kscience.visionforge.*
public interface SolidReference : VisionGroup {
/**
* The prototype for this reference.
*/
public val prototype: Solid
override fun getPropertyValue(
name: Name,
inherit: Boolean,
includeStyles: Boolean,
includeDefaults: Boolean
): Value? {
meta[name]?.value?.let { return it }
if (includeStyles) {
getStyleProperty(name)?.let { return it }
}
prototype.getPropertyValue(name, inherit, includeStyles, includeDefaults)?.let { return it }
if (inherit) {
parent?.getPropertyValue(name, inherit, includeStyles, includeDefaults)?.let { return it }
}
return null
}
}
import space.kscience.visionforge.solid.SolidReference.Companion.REFERENCE_CHILD_PROPERTY_PREFIX
/**
@ -46,103 +23,116 @@ public val Vision.unref: Solid
else -> error("This Vision is neither Solid nor SolidReference")
}
private fun childToken(childName: Name): NameToken =
NameToken(SolidReferenceGroup.REFERENCE_CHILD_PROPERTY_PREFIX, childName.toString())
private fun childPropertyName(childName: Name, propertyName: Name): Name =
childToken(childName) + propertyName
/**
* A reference [Solid] to reuse a template object
* @param name A name of reference child relative to prototype root
*/
@Serializable
@SerialName("solid.ref")
public class SolidReferenceGroup(
public val refName: Name,
) : VisionBase(), SolidReference, VisionGroup, Solid {
internal class SolidReferenceChild(
val owner: SolidReference,
override var parent: Vision?,
val childName: Name,
) : Solid {
/**
* Recursively search for defined template in the parent
*/
override val prototype: Solid by lazy {
if (parent == null) error("No parent is present for SolidReferenceGroup")
if (parent !is PrototypeHolder) error("Parent does not hold prototypes")
(parent as? PrototypeHolder)?.getPrototype(refName) ?: error("Prototype with name $refName not found")
}
val prototype: Solid
get() = owner.prototype.children[childName] as? Solid
?: error("Prototype with name $childName not found")
override val children: Map<NameToken, Vision>
get() = (prototype as? VisionGroup)?.children
?.filter { it.key != SolidGroup.PROTOTYPES_TOKEN }
?.mapValues {
ReferenceChild(this, it.key.asName())
} ?: emptyMap()
override val meta: Meta get() = owner.getProperty(childToken(childName).asName())
override fun getPropertyValue(
name: Name,
inherit: Boolean,
includeStyles: Boolean,
includeDefaults: Boolean
): Value? = super<SolidReference>.getPropertyValue(name, inherit, includeStyles, includeDefaults)
override val descriptor: MetaDescriptor get() = prototype.descriptor
/**
* 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).
*/
private class ReferenceChild(
val owner: SolidReferenceGroup,
private val refName: Name
) : SolidReference, VisionGroup, Solid {
override val prototype: Solid by lazy {
if (refName.isEmpty()) {
owner.prototype
} else {
val proto = (owner.prototype as? VisionGroup)?.get(refName)
?: error("Prototype with name $refName not found in SolidReferenceGroup ${owner.refName}")
proto as? Solid ?: error("Prototype with name $refName is ${proto::class} but expected Solid")
// proto.unref as? Solid
// ?: error("Prototype with name $refName is ${proto::class} but expected Solid")
}
includeDefaults: Boolean,
): Value? {
owner.getPropertyValue(
childPropertyName(childName, name), inherit, includeStyles, includeDefaults
)?.let { return it }
if (includeStyles) {
getStyleProperty(name)?.value?.let { return it }
}
override val meta: ObservableMutableMeta by lazy {
owner.meta.getOrCreate(childToken(refName).asName())
prototype.getPropertyValue(name, inherit, includeStyles, includeDefaults)?.let { return it }
if (inherit) {
parent?.getPropertyValue(name, inherit, includeStyles, includeDefaults)?.let { return it }
}
override val children: Map<NameToken, Vision>
get() = (prototype as? VisionGroup)?.children
?.filter { it.key != SolidGroup.PROTOTYPES_TOKEN }
?.mapValues { (key, _) ->
ReferenceChild(owner, refName + key.asName())
} ?: emptyMap()
override var parent: VisionGroup?
get() {
val parentName = refName.cutLast()
return if (parentName.isEmpty()) owner else ReferenceChild(owner, parentName)
}
set(_) {
error("Setting a parent for a reference child is not possible")
}
override fun invalidateProperty(propertyName: Name) {
owner.invalidateProperty(childPropertyName(refName, propertyName))
}
override fun update(change: VisionChange) {
change.properties?.let {
updateProperties(Name.EMPTY, it)
}
}
override val descriptor: MetaDescriptor get() = prototype.descriptor
return null
}
public companion object {
override fun setProperty(name: Name, node: Meta?) {
owner.setProperty(childPropertyName(childName, name), node)
}
override fun setPropertyValue(name: Name, value: Value?) {
owner.setPropertyValue(childPropertyName(childName, name), value)
}
override val propertyChanges: SharedFlow<Name>
get() = TODO("Not yet implemented")
override fun invalidateProperty(propertyName: Name) {
owner.invalidateProperty(childPropertyName(childName, propertyName))
}
override fun update(change: VisionChange) {
TODO("Not yet implemented")
}
override val children: VisionChildren = object : VisionChildren {
override val parent: Vision get() = this@SolidReferenceChild
override val keys: Set<NameToken> get() = prototype.children.keys
override val changes: Flow<Name> get() = emptyFlow()
override fun get(token: NameToken): SolidReferenceChild? {
if (token !in prototype.children.keys) return null
return SolidReferenceChild(this@SolidReferenceChild.owner, this@SolidReferenceChild, childName + token)
}
}
companion object {
private fun childToken(childName: Name): NameToken =
NameToken(REFERENCE_CHILD_PROPERTY_PREFIX, childName.toString())
private fun childPropertyName(childName: Name, propertyName: Name): Name =
childToken(childName) + propertyName
}
}
@Serializable
@SerialName("solid.ref")
public class SolidReference(
@SerialName("prototype") public val prototypeName: Name,
) : SolidBase() {
/**
* The prototype for this reference.
*/
public val prototype: Solid by lazy {
//Recursively search for defined template in the parent
if (parent == null) error("No parent is present for SolidReference")
if (parent !is PrototypeHolder) error("Parent does not hold prototypes")
(parent as? PrototypeHolder)?.getPrototype(prototypeName)
?: error("Prototype with name $prototypeName not found")
}
override val children: VisionChildren
get() = object : VisionChildren {
override val parent: Vision get() = this@SolidReference
override val keys: Set<NameToken> get() = prototype.children.keys
override val changes: Flow<Name> get() = emptyFlow()
override fun get(token: NameToken): SolidReferenceChild? {
if (token !in prototype.children.keys) return null
return SolidReferenceChild(this@SolidReference, this@SolidReference, token.asName())
}
}
public companion object{
public const val REFERENCE_CHILD_PROPERTY_PREFIX: String = "@child"
}
}
@ -150,33 +140,123 @@ public class SolidReferenceGroup(
/**
* Create ref for existing prototype
*/
public fun SolidGroup.ref(
public fun VisionContainerBuilder<Solid>.ref(
templateName: Name,
name: String? = null,
): SolidReferenceGroup = SolidReferenceGroup(templateName).also { set(name, it) }
): SolidReference = SolidReference(templateName).also { set(name, it) }
public fun SolidGroup.ref(
public fun VisionContainerBuilder<Solid>.ref(
templateName: String,
name: String? = null,
): SolidReferenceGroup = ref(Name.parse(templateName), name)
): SolidReference = ref(Name.parse(templateName), name)
/**
* Add new [SolidReferenceGroup] wrapping given object and automatically adding it to the prototypes.
* One must ensure that [prototypeHolder] is a parent of this group.
* Add new [SolidReference] wrapping given object and automatically adding it to the prototypes.
*/
public fun SolidGroup.newRef(
name: String?,
obj: Solid,
prototypeHolder: PrototypeHolder = this,
templateName: Name = Name.parse(name ?: obj.toString()),
): SolidReferenceGroup {
val existing = getPrototype(templateName)
prototypeHolder: SolidGroup = this,
prototypeName: Name = Name.parse(name ?: obj.toString()),
): SolidReference {
val existing = prototypeHolder.getPrototype(prototypeName)
if (existing == null) {
prototypeHolder.prototypes {
set(templateName, obj)
set(prototypeName, obj)
}
} else if (existing != obj) {
error("Can't add different prototype on top of existing one")
}
return ref(templateName, name)
return children.ref(prototypeName, name)
}
//
//
///**
// * A reference [Solid] to reuse a template object
// */
//@Serializable
//@SerialName("solid.ref")
//public class SolidReferenceGroup(
// public val refName: Name,
//) : VisionGroup(), SolidReference, VisionGroup<Solid>, Solid {
//
// /**
// * Recursively search for defined template in the parent
// */
// override val prototype: Solid by lazy {
// if (parent == null) error("No parent is present for SolidReferenceGroup")
// if (parent !is PrototypeHolder) error("Parent does not hold prototypes")
// (parent as? PrototypeHolder)?.getPrototype(refName) ?: error("Prototype with name $refName not found")
// }
//
// override val items: Map<NameToken, VisionGroupItem<Solid>>
// get() = (prototype as? VisionGroup<*>)?.items
// ?.filter { it.key != SolidGroup.PROTOTYPES_TOKEN }
// ?.mapValues {
// VisionGroupItem.Node(ReferenceChild(this, it.key.asName()))
// } ?: emptyMap()
//
// override val descriptor: MetaDescriptor get() = prototype.descriptor
//
//
// /**
// * 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).
// */
// private class ReferenceChild(
// val owner: SolidReferenceGroup,
// private val refName: Name,
// ) : SolidReference, VisionGroup<Solid>, Solid {
//
// override val prototype: Solid by lazy {
// if (refName.isEmpty()) {
// owner.prototype
// } else {
// val proto = (owner.prototype).children.get(refName)
// ?: error("Prototype with name $refName not found in SolidReferenceGroup ${owner.refName}")
// proto as? Solid ?: error("Prototype with name $refName is ${proto::class} but expected Solid")
//// proto.unref as? Solid
//// ?: error("Prototype with name $refName is ${proto::class} but expected Solid")
// }
// }
//
// override val meta: ObservableMutableMeta by lazy {
// owner.meta.getOrCreate(childToken(refName).asName())
// }
//
// override val items: Map<NameToken, VisionGroupItem<Solid>>
// get() = (prototype as? VisionGroup<*>)?.items
// ?.filter { it.key != SolidGroup.PROTOTYPES_TOKEN }
// ?.mapValues { (key, _) ->
// VisionGroupItem.Node(ReferenceChild(owner, refName + key.asName()))
// } ?: emptyMap()
//
// override var parent: VisionGroup<*>?
// get() {
// val parentName = refName.cutLast()
// return if (parentName.isEmpty()) owner else ReferenceChild(owner, parentName)
// }
// set(_) {
// error("Setting a parent for a reference child is not possible")
// }
//
// override fun invalidateProperty(propertyName: Name) {
// owner.invalidateProperty(childPropertyName(refName, propertyName))
// }
//
// override fun update(change: VisionChange) {
// change.properties?.let {
// updateProperties(it, Name.EMPTY)
// }
// }
//
// override val descriptor: MetaDescriptor get() = prototype.descriptor
//
// }
//
// public companion object {
// public const val REFERENCE_CHILD_PROPERTY_PREFIX: String = "@child"
// }
//}

View File

@ -29,7 +29,7 @@ public class Solids(meta: Meta) : VisionPlugin(meta) {
private fun PolymorphicModuleBuilder<Solid>.solids() {
subclass(SolidGroup.serializer())
subclass(SolidReferenceGroup.serializer())
subclass(SolidReference.serializer())
subclass(Composite.serializer())
subclass(Box.serializer())
subclass(GenericHexagon.serializer())
@ -47,8 +47,7 @@ public class Solids(meta: Meta) : VisionPlugin(meta) {
public val serializersModuleForSolids: SerializersModule = SerializersModule {
polymorphic(Vision::class) {
subclass(VisionBase.serializer())
subclass(VisionGroupBase.serializer())
subclass(VisionGroup.serializer())
solids()
}

View File

@ -21,7 +21,7 @@ public class Sphere(
) : SolidBase(), GeometrySolid, VisionPropertyContainer<Sphere> {
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 {
// This transformation matches three.js sphere implementation
val y = r * cos(theta)
val z = r * sin(theta) * sin(phi)
@ -39,10 +39,10 @@ public class Sphere(
for (j in 0 until segments) { // phi iteration
val phi1 = phiStart + j * phiStep
val phi2 = phi1 + phiStep
val point1 = point3DfromSphCoord(radius, theta1, phi1)
val point2 = point3DfromSphCoord(radius, theta1, phi2)
val point3 = point3DfromSphCoord(radius, theta2, phi2)
val point4 = point3DfromSphCoord(radius, theta2, phi1)
val point1 = point3dFromSphCoord(radius, theta1, phi1)
val point2 = point3dFromSphCoord(radius, theta1, phi2)
val point3 = point3dFromSphCoord(radius, theta2, phi2)
val point4 = point3dFromSphCoord(radius, theta2, phi1)
geometryBuilder.apply {
// 1-2-3-4 gives the same face but with opposite orientation
face4(point1, point4, point3, point2)

View File

@ -27,7 +27,7 @@ public class SphereLayer(
require(outerRadius > 0) { "Outer radius must be positive" }
require(innerRadius >= 0) { "inner radius must be non-negative" }
fun point3DfromSphCoord(r: Float, theta: Float, phi: Float): Point3D {
fun point3dFromSphCoord(r: Float, theta: Float, phi: Float): Point3D {
// This transformation matches three.js sphere implementation
val y = r * cos(theta)
val z = r * sin(theta) * sin(phi)
@ -46,17 +46,17 @@ public class SphereLayer(
val phi1 = phiStart + j * phiStep
val phi2 = phi1 + phiStep
//outer points
val outerPoint1 = point3DfromSphCoord(outerRadius, theta1, phi1)
val outerPoint2 = point3DfromSphCoord(outerRadius, theta1, phi2)
val outerPoint3 = point3DfromSphCoord(outerRadius, theta2, phi2)
val outerPoint4 = point3DfromSphCoord(outerRadius, theta2, phi1)
val outerPoint1 = point3dFromSphCoord(outerRadius, theta1, phi1)
val outerPoint2 = point3dFromSphCoord(outerRadius, theta1, phi2)
val outerPoint3 = point3dFromSphCoord(outerRadius, theta2, phi2)
val outerPoint4 = point3dFromSphCoord(outerRadius, theta2, phi1)
// 1-2-3-4 gives the same face but with opposite orientation
face4(outerPoint1, outerPoint4, outerPoint3, outerPoint2)
if (innerRadius > 0) {
val innerPoint1 = point3DfromSphCoord(innerRadius, theta1, phi1)
val innerPoint2 = point3DfromSphCoord(innerRadius, theta1, phi2)
val innerPoint3 = point3DfromSphCoord(innerRadius, theta2, phi2)
val innerPoint4 = point3DfromSphCoord(innerRadius, theta2, phi1)
val innerPoint1 = point3dFromSphCoord(innerRadius, theta1, phi1)
val innerPoint2 = point3dFromSphCoord(innerRadius, theta1, phi2)
val innerPoint3 = point3dFromSphCoord(innerRadius, theta2, phi2)
val innerPoint4 = point3dFromSphCoord(innerRadius, theta2, phi1)
face4(innerPoint1, innerPoint2, innerPoint3, innerPoint4)
//the cup
if (i == segments - 1 && theta != PI.toFloat() && innerRadius != outerRadius) {

View File

@ -112,9 +112,9 @@ public fun Point3D.toMeta(): Meta = Meta {
internal fun Meta.toVector(default: Float = 0f) = Point3D(
this[Solid.X_KEY].float ?: default,
this[Solid.Y_KEY].float ?: default,
this[Solid.Z_KEY].float ?: default
this[X_KEY].float ?: default,
this[Y_KEY].float ?: default,
this[Z_KEY].float ?: default
)
//internal fun Solid.updatePosition(meta: Meta?) {

View File

@ -5,6 +5,7 @@ import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.scheme
import space.kscience.dataforge.meta.descriptors.value
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.values.set
import space.kscience.visionforge.hide
import space.kscience.visionforge.widgetType

View File

@ -1,31 +1,26 @@
package space.kscience.visionforge.solid.transform
import space.kscience.dataforge.meta.configure
import space.kscience.dataforge.meta.update
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.visionforge.*
import space.kscience.visionforge.getProperty
import space.kscience.visionforge.solid.*
private operator fun Number.plus(other: Number) = toFloat() + other.toFloat()
private operator fun Number.times(other: Number) = toFloat() * other.toFloat()
@DFExperimental
internal fun Vision.updateFrom(other: Vision): Vision {
if (this is Solid && other is Solid) {
x += other.x
y += other.y
z += other.y
rotationX += other.rotationX
rotationY += other.rotationY
rotationZ += other.rotationZ
scaleX *= other.scaleX
scaleY *= other.scaleY
scaleZ *= other.scaleZ
configure{
update(other.meta)
}
}
internal fun Solid.updateFrom(other: Solid): Solid {
x += other.x
y += other.y
z += other.y
rotationX += other.rotationX
rotationY += other.rotationY
rotationZ += other.rotationZ
scaleX *= other.scaleX
scaleY *= other.scaleY
scaleZ *= other.scaleZ
setProperty(Name.EMPTY, other.getProperty(Name.EMPTY))
return this
}
@ -34,17 +29,17 @@ internal fun Vision.updateFrom(other: Vision): Vision {
internal object RemoveSingleChild : VisualTreeTransform<SolidGroup>() {
override fun SolidGroup.transformInPlace() {
fun MutableVisionGroup.replaceChildren() {
children.forEach { (childName, parent) ->
if (parent is SolidReferenceGroup) return@forEach //ignore refs
if (parent is MutableVisionGroup) {
fun SolidGroup.replaceChildren() {
items.forEach { (childName, parent) ->
if (parent is SolidReference) return@forEach //ignore refs
if (parent is SolidGroup) {
parent.replaceChildren()
}
if (parent is VisionGroup && parent.children.size == 1) {
val child = parent.children.values.first()
if (parent is SolidGroup && parent.items.size == 1) {
val child: Solid = parent.items.values.first()
val newParent = child.updateFrom(parent)
newParent.parent = null
set(childName.asName(), newParent)
children[childName.asName()] = newParent
}
}
}

View File

@ -2,41 +2,47 @@ package space.kscience.visionforge.solid.transform
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.visionforge.MutableVisionGroup
import space.kscience.visionforge.VisionGroup
import space.kscience.visionforge.solid.SolidGroup
import space.kscience.visionforge.solid.SolidReferenceGroup
import space.kscience.visionforge.solid.SolidReference
import kotlin.collections.HashMap
import kotlin.collections.Map
import kotlin.collections.component1
import kotlin.collections.component2
import kotlin.collections.filter
import kotlin.collections.filterIsInstance
import kotlin.collections.fold
import kotlin.collections.forEach
import kotlin.collections.set
@DFExperimental
internal object UnRef : VisualTreeTransform<SolidGroup>() {
private fun VisionGroup.countRefs(): Map<Name, Int> {
return children.values.fold(HashMap()) { reducer, obj ->
if (obj is VisionGroup) {
val counter = obj.countRefs()
private fun SolidGroup.countRefs(): Map<Name, Int> {
return items.values.fold(HashMap()) { reducer, vision ->
if (vision is SolidGroup) {
val counter = vision.countRefs()
counter.forEach { (key, value) ->
reducer[key] = (reducer[key] ?: 0) + value
}
} else if (obj is SolidReferenceGroup) {
reducer[obj.refName] = (reducer[obj.refName] ?: 0) + 1
} else if (vision is SolidReference) {
reducer[vision.prototypeName] = (reducer[vision.prototypeName] ?: 0) + 1
}
return reducer
}
}
private fun MutableVisionGroup.unref(name: Name) {
private fun SolidGroup.unref(name: Name) {
(this as? SolidGroup)?.prototypes{
set(name, null)
}
children.filter { (it.value as? SolidReferenceGroup)?.refName == name }.forEach { (key, value) ->
val reference = value as SolidReferenceGroup
items.filter { (it.value as? SolidReference)?.prototypeName == name }.forEach { (key, value) ->
val reference = value as SolidReference
val newChild = reference.prototype.updateFrom(reference)
newChild.parent = null
set(key.asName(), newChild) // replace proxy with merged object
children[key] = newChild // replace proxy with merged object
}
children.values.filterIsInstance<MutableVisionGroup>().forEach { it.unref(name) }
items.values.filterIsInstance<SolidGroup>().forEach { it.unref(name) }
}
override fun SolidGroup.transformInPlace() {

View File

@ -18,7 +18,7 @@ class CompositeTest {
detail = 32
}
material {
color("pink")
color.set("pink")
}
}
}

View File

@ -1,7 +1,6 @@
package space.kscience.visionforge.solid
import space.kscience.dataforge.meta.getIndexed
import space.kscience.dataforge.meta.node
import space.kscience.dataforge.meta.toMeta
import space.kscience.dataforge.misc.DFExperimental
import kotlin.test.Test
@ -12,7 +11,7 @@ class ConvexTest {
@Suppress("UNUSED_VARIABLE")
@Test
fun testConvexBuilder() {
val group = SolidGroup().apply {
val group = SolidGroup{
convex {
point(50, 50, -50)
point(50, -50, -50)
@ -25,7 +24,7 @@ class ConvexTest {
}
}
val convex = group.children.values.first() as Convex
val convex = group.items.values.first() as Convex
val json = Solids.jsonForSolids.encodeToJsonElement(Convex.serializer(), convex)
val meta = json.toMeta()

View File

@ -9,7 +9,7 @@ import kotlin.test.assertEquals
class GroupTest {
@Test
fun testGroupWithComposite() {
val group = SolidGroup().apply {
val group = SolidGroup{
union("union") {
box(100, 100, 100) {
z = 100
@ -18,7 +18,7 @@ class GroupTest {
}
box(100, 100, 100)
material {
color(Colors.lightgreen)
color.set(Colors.lightgreen)
opacity = 0.3f
}
}
@ -30,7 +30,7 @@ class GroupTest {
}
box(100, 100, 100)
y = 300
color(Colors.red)
color.set(Colors.red)
}
subtract("subtract") {
box(100, 100, 100) {
@ -40,12 +40,12 @@ class GroupTest {
}
box(100, 100, 100)
y = -300
color(Colors.blue)
color.set(Colors.blue)
}
}
assertEquals(3, group.children.count())
assertEquals(300.0, (group["intersect"] as Solid).y.toDouble())
assertEquals(-300.0, (group["subtract"] as Solid).y.toDouble())
assertEquals(3, group.items.count())
assertEquals(300.0, (group.children["intersect"] as Solid).y.toDouble())
assertEquals(-300.0, (group.children["subtract"] as Solid).y.toDouble())
}
}

View File

@ -15,7 +15,7 @@ class PropertyTest {
val box = Box(10.0f, 10.0f,10.0f)
box.material {
//meta["color"] = "pink"
color("pink")
color.set("pink")
}
assertEquals("pink", box.meta["material.color"]?.string)
assertEquals("pink", box.color.string)
@ -33,7 +33,7 @@ class PropertyTest {
}
box.material {
color("pink")
color.set("pink")
}
assertEquals("pink", c)
@ -43,7 +43,7 @@ class PropertyTest {
fun testInheritedProperty() {
var box: Box? = null
val group = SolidGroup().apply {
setPropertyNode("test", 22)
setPropertyValue("test", 22)
group {
box = box(100, 100, 100)
}
@ -54,14 +54,14 @@ class PropertyTest {
@Test
fun testStyleProperty() {
var box: Box? = null
val group = SolidGroup().apply {
val group = SolidGroup{
styleSheet {
set("testStyle") {
update("testStyle") {
"test" put 22
}
}
group {
box = box(100, 100, 100).apply {
box = box(100, 100, 100) {
useStyle("testStyle")
}
}
@ -74,7 +74,7 @@ class PropertyTest {
var box: Box? = null
val group = SolidGroup().apply {
styleSheet {
set("testStyle") {
update("testStyle") {
SolidMaterial.MATERIAL_COLOR_KEY put "#555555"
}
}
@ -89,10 +89,10 @@ class PropertyTest {
@Test
fun testReferenceStyleProperty() {
var box: SolidReferenceGroup? = null
var box: SolidReference? = null
val group = SolidGroup{
styleSheet {
set("testStyle") {
update("testStyle") {
SolidMaterial.MATERIAL_COLOR_KEY put "#555555"
}
}
@ -105,6 +105,6 @@ class PropertyTest {
box = ref("box".asName())
}
}
assertEquals("#555555", box?.color.string)
assertEquals("#555555", box!!.color.string)
}
}

View File

@ -2,7 +2,6 @@ package space.kscience.visionforge.solid
import space.kscience.dataforge.names.Name
import space.kscience.visionforge.Colors
import space.kscience.visionforge.MutableVisionGroup
import space.kscience.visionforge.get
import kotlin.test.Test
import kotlin.test.assertEquals
@ -14,10 +13,10 @@ import kotlin.test.assertEquals
fun SolidGroup.refGroup(
name: String,
templateName: Name = Name.parse(name),
block: MutableVisionGroup.() -> Unit
): SolidReferenceGroup {
block: SolidGroup.() -> Unit
): SolidReference {
val group = SolidGroup().apply(block)
return newRef(name, group, templateName = templateName)
return newRef(name, group, prototypeName = templateName)
}
@ -25,7 +24,7 @@ class SerializationTest {
@Test
fun testCubeSerialization() {
val cube = Box(100f, 100f, 100f).apply {
color(222)
color.set(222)
x = 100
z = -100
}
@ -38,7 +37,7 @@ class SerializationTest {
@Test
fun testProxySerialization() {
val cube = Box(100f, 100f, 100f).apply {
color(222)
color.set(222)
x = 100
z = -100
}
@ -53,21 +52,21 @@ class SerializationTest {
val string = Solids.encodeToString(group)
println(string)
val reconstructed = Solids.decodeFromString(string) as SolidGroup
assertEquals(group["cube"]?.meta, reconstructed["cube"]?.meta)
assertEquals(group.children["cube"]?.meta, reconstructed.children["cube"]?.meta)
}
@Test
fun lightSerialization(){
val group = SolidGroup {
ambientLight {
color(Colors.white)
color.set(Colors.white)
intensity = 100.0
}
}
val serialized = Solids.encodeToString(group)
val reconstructed = Solids.decodeFromString(serialized) as SolidGroup
assertEquals(100.0, (reconstructed["@ambientLight"] as AmbientLightSource).intensity.toDouble())
assertEquals(100.0, (reconstructed.children["@ambientLight"] as AmbientLightSource).intensity.toDouble())
}
}

View File

@ -24,6 +24,9 @@ class SolidPluginTest {
val reconstructed = visionManager.decodeFromMeta(meta) as SolidGroup
assertEquals(visionManager.encodeToJsonElement(vision["aBox"]!!), visionManager.encodeToJsonElement(reconstructed["aBox"]!!))
assertEquals(
visionManager.encodeToJsonElement(vision.children["aBox"]!!),
visionManager.encodeToJsonElement(reconstructed.children["aBox"]!!)
)
}
}

View File

@ -15,7 +15,7 @@ class SolidReferenceTest {
SolidMaterial.MATERIAL_COLOR_KEY put "red"
}
newRef("test", Box(100f,100f,100f).apply {
color("blue")
color.set("blue")
useStyle(theStyle)
})
}
@ -23,13 +23,13 @@ class SolidReferenceTest {
@Test
fun testReferenceProperty(){
assertEquals("blue", (groupWithReference["test"] as Solid).color.string)
assertEquals("blue", (groupWithReference.children["test"] as Solid).color.string)
}
@Test
fun testReferenceSerialization(){
val serialized = Solids.jsonForSolids.encodeToJsonElement(groupWithReference)
val deserialized = Solids.jsonForSolids.decodeFromJsonElement(SolidGroup.serializer(), serialized)
assertEquals("blue", (deserialized["test"] as Solid).color.string)
assertEquals("blue", (deserialized.children["test"] as Solid).color.string)
}
}

View File

@ -21,24 +21,24 @@ class VisionUpdateTest {
box(200,200,200, name = "origin")
}
val dif = VisionChange{
group("top") {
color(123)
group ("top") {
color.set(123)
box(100,100,100)
}
propertyChanged("top".asName(), SolidMaterial.MATERIAL_COLOR_KEY, Meta("red".asValue()))
propertyChanged("origin".asName(), SolidMaterial.MATERIAL_COLOR_KEY, Meta("red".asValue()))
}
targetVision.update(dif)
assertTrue { targetVision["top"] is SolidGroup }
assertEquals("red", (targetVision["origin"] as Solid).color.string) // Should work
assertEquals("#00007b", (targetVision["top"] as Solid).color.string) // new item always takes precedence
assertTrue { targetVision.children["top"] is SolidGroup }
assertEquals("red", (targetVision.children["origin"] as Solid).color.string) // Should work
assertEquals("#00007b", (targetVision.children["top"] as Solid).color.string) // new item always takes precedence
}
@Test
fun testVisionChangeSerialization(){
val change = VisionChange{
group("top") {
color(123)
color.set(123)
box(100,100,100)
}
propertyChanged("top".asName(), SolidMaterial.MATERIAL_COLOR_KEY, Meta("red".asValue()))

View File

@ -12,8 +12,9 @@ import space.kscience.dataforge.values.Null
import space.kscience.dataforge.values.Value
import space.kscience.dataforge.values.asValue
import space.kscience.tables.*
import space.kscience.visionforge.VisionBase
import space.kscience.visionforge.VisionGroup
import space.kscience.visionforge.html.VisionOutput
import space.kscience.visionforge.properties
import kotlin.jvm.JvmName
import kotlin.reflect.typeOf
@ -41,12 +42,13 @@ public val ColumnHeader<Value>.properties: ValueColumnScheme get() = ValueColumn
@SerialName("vision.table")
public class VisionOfTable(
override val headers: List<@Serializable(ColumnHeaderSerializer::class) ColumnHeader<Value>>,
) : VisionBase(), Rows<Value> {
) : VisionGroup(), Rows<Value> {
public var data: List<Meta>
get() = meta.getIndexed("rows").entries.sortedBy { it.key?.toInt() }.map { it.value }
set(value) {
meta["rows"] = value
//TODO Make it better
properties()["rows"] = value
}
public val rows: List<MetaRow> get() = data.map(::MetaRow)

View File

@ -4,16 +4,15 @@ import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.geometries.EdgesGeometry
import info.laht.threekt.objects.LineSegments
import info.laht.threekt.objects.Mesh
import space.kscience.dataforge.meta.updateWith
import space.kscience.dataforge.meta.boolean
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.plus
import space.kscience.dataforge.names.startsWith
import space.kscience.dataforge.values.boolean
import space.kscience.visionforge.VisionBuilder
import space.kscience.visionforge.computePropertyNode
import space.kscience.visionforge.getProperty
import space.kscience.visionforge.onPropertyChange
import space.kscience.visionforge.setProperty
import space.kscience.visionforge.setPropertyValue
import space.kscience.visionforge.solid.Solid
import space.kscience.visionforge.solid.SolidMaterial
import space.kscience.visionforge.solid.layer
@ -32,7 +31,7 @@ public abstract class MeshThreeFactory<in T : Solid>(
*/
public abstract fun buildGeometry(obj: T): BufferGeometry
override fun invoke(three: ThreePlugin, obj: T): Mesh {
override fun build(three: ThreePlugin, obj: T): Mesh {
val geometry = buildGeometry(obj)
//val meshMeta: Meta = obj.properties[Material3D.MATERIAL_KEY]?.node ?: Meta.empty
@ -78,8 +77,8 @@ public abstract class MeshThreeFactory<in T : Solid>(
@VisionBuilder
public fun Solid.edges(enabled: Boolean = true, block: SolidMaterial.() -> Unit = {}) {
setProperty(EDGES_ENABLED_KEY, enabled)
meta.getOrCreate(EDGES_MATERIAL_KEY).updateWith(SolidMaterial, block)
setPropertyValue(EDGES_ENABLED_KEY, enabled)
SolidMaterial.write(getProperty(EDGES_MATERIAL_KEY)).apply(block)
}
internal fun Mesh.applyProperties(obj: Solid): Mesh = apply {
@ -95,9 +94,9 @@ internal fun Mesh.applyProperties(obj: Solid): Mesh = apply {
public fun Mesh.applyEdges(obj: Solid) {
val edges = children.find { it.name == "@edges" } as? LineSegments
//inherited edges definition, enabled by default
if (obj.getPropertyValue(EDGES_ENABLED_KEY, inherit = true)?.boolean != false) {
if (obj.getProperty(EDGES_ENABLED_KEY, inherit = true).boolean != false) {
val bufferGeometry = geometry as? BufferGeometry ?: return
val material = ThreeMaterials.getLineMaterial(obj.computePropertyNode(EDGES_MATERIAL_KEY), true)
val material = ThreeMaterials.getLineMaterial(obj.getProperty(EDGES_MATERIAL_KEY), true)
if (edges == null) {
add(
LineSegments(

View File

@ -8,7 +8,7 @@ import kotlin.reflect.KClass
public object ThreeAmbientLightFactory : ThreeFactory<AmbientLightSource> {
override val type: KClass<in AmbientLightSource> get() = AmbientLightSource::class
override fun invoke(three: ThreePlugin, obj: AmbientLightSource): AmbientLight {
override fun build(three: ThreePlugin, obj: AmbientLightSource): AmbientLight {
val res = AmbientLight().apply {
color = obj.color.threeColor() ?: Color(0x404040)
intensity = obj.intensity.toDouble()

View File

@ -11,6 +11,7 @@ import org.w3c.dom.CanvasRenderingContext2D
import org.w3c.dom.CanvasTextBaseline
import org.w3c.dom.HTMLCanvasElement
import org.w3c.dom.MIDDLE
import space.kscience.visionforge.getProperty
import space.kscience.visionforge.solid.SolidLabel
import space.kscience.visionforge.solid.SolidMaterial
import space.kscience.visionforge.solid.three.ThreeCanvas.Companion.DO_NOT_HIGHLIGHT_TAG
@ -22,11 +23,11 @@ import kotlin.reflect.KClass
public object ThreeCanvasLabelFactory : ThreeFactory<SolidLabel> {
override val type: KClass<in SolidLabel> get() = SolidLabel::class
override fun invoke(three: ThreePlugin, obj: SolidLabel): Object3D {
override fun build(three: ThreePlugin, obj: SolidLabel): Object3D {
val canvas = document.createElement("canvas") as HTMLCanvasElement
val context = canvas.getContext("2d") as CanvasRenderingContext2D
context.font = "Bold ${obj.fontSize}pt ${obj.fontFamily}"
context.fillStyle = obj.getPropertyValue(SolidMaterial.MATERIAL_COLOR_KEY)?.value ?: "black"
context.fillStyle = obj.getProperty(SolidMaterial.MATERIAL_COLOR_KEY)?.value ?: "black"
context.textBaseline = CanvasTextBaseline.MIDDLE
val metrics = context.measureText(obj.text)
//canvas.width = metrics.width.toInt()

View File

@ -37,7 +37,7 @@ public class ThreeCompositeFactory(public val three: ThreePlugin) : ThreeFactory
override val type: KClass<in Composite> get() = Composite::class
override fun invoke(three: ThreePlugin, obj: Composite): Mesh {
override fun build(three: ThreePlugin, obj: Composite): Mesh {
val first = three.buildObject3D(obj.first).takeIfMesh() ?: error("First part of composite is not a mesh")
val second = three.buildObject3D(obj.second).takeIfMesh() ?: error("Second part of composite is not a mesh")
return when (obj.compositeType) {

View File

@ -22,7 +22,7 @@ public interface ThreeFactory<in T : Vision> {
public val type: KClass<in T>
public operator fun invoke(three: ThreePlugin, obj: T): Object3D
public fun build(three: ThreePlugin, obj: T): Object3D
public companion object {
public const val TYPE: String = "threeFactory"

View File

@ -17,7 +17,7 @@ import kotlin.reflect.KClass
public object ThreeLabelFactory : ThreeFactory<SolidLabel> {
override val type: KClass<in SolidLabel> get() = SolidLabel::class
override fun invoke(three: ThreePlugin, obj: SolidLabel): Object3D {
override fun build(three: ThreePlugin, obj: SolidLabel): Object3D {
val textGeo = TextBufferGeometry(obj.text, jso {
font = obj.fontFamily
size = 20

View File

@ -4,7 +4,7 @@ import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.Object3D
import info.laht.threekt.math.Color
import info.laht.threekt.objects.LineSegments
import space.kscience.visionforge.computePropertyNode
import space.kscience.visionforge.getProperty
import space.kscience.visionforge.onPropertyChange
import space.kscience.visionforge.solid.PolyLine
import space.kscience.visionforge.solid.SolidMaterial
@ -17,7 +17,7 @@ import kotlin.reflect.KClass
public object ThreeLineFactory : ThreeFactory<PolyLine> {
override val type: KClass<PolyLine> get() = PolyLine::class
override fun invoke(three: ThreePlugin, obj: PolyLine): Object3D {
override fun build(three: ThreePlugin, obj: PolyLine): Object3D {
val geometry = BufferGeometry().apply {
setFromPoints(Array((obj.points.size - 1) * 2) {
obj.points[ceil(it / 2.0).toInt()].toVector()
@ -25,7 +25,7 @@ public object ThreeLineFactory : ThreeFactory<PolyLine> {
}
val material = ThreeMaterials.getLineMaterial(
obj.computePropertyNode(SolidMaterial.MATERIAL_KEY),
obj.getProperty(SolidMaterial.MATERIAL_KEY),
false
)

View File

@ -171,7 +171,7 @@ public fun Mesh.updateMaterialProperty(vision: Vision, propertyName: Name) {
?: ThreeMaterials.BLACK_COLOR
}
SolidMaterial.MATERIAL_OPACITY_KEY -> {
val opacity = vision.getPropertyValue(
val opacity = vision.getProperty(
SolidMaterial.MATERIAL_OPACITY_KEY,
inherit = true,
)?.double ?: 1.0
@ -179,7 +179,7 @@ public fun Mesh.updateMaterialProperty(vision: Vision, propertyName: Name) {
material.transparent = opacity < 1.0
}
SolidMaterial.MATERIAL_WIREFRAME_KEY -> {
material.asDynamic().wireframe = vision.getPropertyValue(
material.asDynamic().wireframe = vision.getProperty(
SolidMaterial.MATERIAL_WIREFRAME_KEY,
inherit = true,
)?.boolean ?: false

View File

@ -12,7 +12,6 @@ import space.kscience.visionforge.Vision
import space.kscience.visionforge.onPropertyChange
import space.kscience.visionforge.solid.*
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
import space.kscience.visionforge.solid.three.set
import space.kscience.visionforge.visible
import kotlin.collections.set
import kotlin.reflect.KClass
@ -49,10 +48,10 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
public fun buildObject3D(obj: Solid): Object3D = when (obj) {
is ThreeJsVision -> obj.render(this)
is SolidReferenceGroup -> ThreeReferenceFactory(this, obj)
is SolidReferenceGroup -> ThreeReferenceFactory.build(this, obj)
is SolidGroup -> {
val group = ThreeGroup()
obj.children.forEach { (token, child) ->
obj.items.forEach { (token, child) ->
if (child is Solid && token != SolidGroup.PROTOTYPES_TOKEN && child.ignore != true) {
try {
val object3D = buildObject3D(child)
@ -101,13 +100,13 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
}
}
}
is Composite -> compositeFactory(this, obj)
is Composite -> compositeFactory.build(this, obj)
else -> {
//find specialized factory for this type if it is present
val factory: ThreeFactory<Solid>? = findObjectFactory(obj::class)
when {
factory != null -> factory(this, obj)
obj is GeometrySolid -> ThreeShapeFactory(this, obj)
factory != null -> factory.build(this, obj)
obj is GeometrySolid -> ThreeShapeFactory.build(this, obj)
else -> error("Renderer for ${obj::class} not found")
}
}

View File

@ -11,17 +11,19 @@ import kotlin.reflect.KClass
public object ThreePointLightFactory : ThreeFactory<PointLightSource> {
override val type: KClass<in PointLightSource> get() = PointLightSource::class
override fun invoke(three: ThreePlugin, obj: PointLightSource): PointLight {
private val DEFAULT_COLOR = Color(0x404040)
override fun build(three: ThreePlugin, obj: PointLightSource): PointLight {
val res = PointLight().apply {
matrixAutoUpdate = false
color = obj.color.threeColor() ?: Color(0x404040)
color = obj.color.threeColor() ?: DEFAULT_COLOR
intensity = obj.intensity.toDouble()
updatePosition(obj)
}
obj.onPropertyChange { name ->
when (name) {
LightSource::color.name.asName() -> res.color = obj.color.threeColor() ?: Color(0x404040)
LightSource::color.name.asName() -> res.color = obj.color.threeColor() ?: DEFAULT_COLOR
LightSource::intensity.name.asName() -> res.intensity = obj.intensity.toDouble()
else -> res.updateProperty(obj, name)
}
@ -29,4 +31,5 @@ public object ThreePointLightFactory : ThreeFactory<PointLightSource> {
return res
}
}

View File

@ -30,7 +30,7 @@ public object ThreeReferenceFactory : ThreeFactory<SolidReferenceGroup> {
}
}
override fun invoke(three: ThreePlugin, obj: SolidReferenceGroup): Object3D {
override fun build(three: ThreePlugin, obj: SolidReferenceGroup): Object3D {
val template = obj.prototype
val cachedObject = cache.getOrPut(template) {
three.buildObject3D(template)
@ -50,7 +50,7 @@ public object ThreeReferenceFactory : ThreeFactory<SolidReferenceGroup> {
if (name.firstOrNull()?.body == REFERENCE_CHILD_PROPERTY_PREFIX) {
val childName = name.firstOrNull()?.index?.let(Name::parse) ?: error("Wrong syntax for reference child property: '$name'")
val propertyName = name.cutFirst()
val referenceChild = obj[childName] ?: error("Reference child with name '$childName' not found")
val referenceChild = obj.children[childName] ?: error("Reference child with name '$childName' not found")
val child = object3D.findChild(childName) ?: error("Object child with name '$childName' not found")
child.updateProperty(referenceChild, propertyName)
} else {