From a5eba1789b35e6124e198be5a71fef6085744042 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 26 Aug 2020 10:16:55 +0300 Subject: [PATCH] GDML optimization --- demo/gdml/build.gradle.kts | 3 +- .../vision/gdml/demo/GDMLAppComponent.kt | 42 ++- .../demo/{GDMLDemoApp.kt => GdmlJsDemoApp.kt} | 31 +- .../demo/{GDMLDemoApp.kt => GdmlFxDemoApp.kt} | 11 +- .../vision/solid/demo/VariableBox.kt | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 58910 -> 59203 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 2 +- gradlew.bat | 21 +- .../dataforge/vision/bootstrap/bootstrap.kt | 33 +- .../hep/dataforge/vision/VisionGroup.kt | 5 +- .../vision/visitor/StatisticsVisitor.kt | 35 +++ .../dataforge/vision/visitor/VisionVisitor.kt | 55 ++++ .../dataforge/vision/visitor/countDistinct.kt | 16 + .../dataforge/vision/gdml/GDMLTransformer.kt | 287 +++++++++++++++++- .../dataforge/vision/gdml/GdmlOptimizer.kt | 89 ++++++ .../hep/dataforge/vision/gdml/GdmlVision.kt | 268 ---------------- .../hep/dataforge/vision/gdml/gdmlJs.kt | 8 + .../gdml/{visualGDMLJvm.kt => gdmlJVM.kt} | 4 +- .../dataforge/vision/gdml/TestConvertor.kt | 5 +- .../dataforge/vision/gdml/bmanStatistics.kt | 34 +++ .../hep/dataforge/vision/solid/Proxy.kt | 6 +- .../hep/dataforge/vision/solid/Solid.kt | 4 +- .../vision/solid/three/MeshThreeFactory.kt | 43 +-- .../vision/solid/three/ThreeFactory.kt | 2 +- .../vision/solid/three/ThreeLabelFactory.kt | 2 +- .../vision/solid/three/ThreeLineFactory.kt | 2 +- .../vision/solid/three/ThreeMaterials.kt | 34 ++- .../vision/solid/three/ThreePlugin.kt | 2 +- .../vision/solid/three/ThreeProxyFactory.kt | 24 +- .../info/laht/threekt/objects/LineSegments.kt | 3 + 31 files changed, 671 insertions(+), 404 deletions(-) rename demo/gdml/src/jsMain/kotlin/hep/dataforge/vision/gdml/demo/{GDMLDemoApp.kt => GdmlJsDemoApp.kt} (52%) rename demo/gdml/src/jvmMain/kotlin/hep/dataforge/vision/gdml/demo/{GDMLDemoApp.kt => GdmlFxDemoApp.kt} (86%) create mode 100644 visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/visitor/StatisticsVisitor.kt create mode 100644 visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/visitor/VisionVisitor.kt create mode 100644 visionforge-core/src/jvmMain/kotlin/hep/dataforge/vision/visitor/countDistinct.kt create mode 100644 visionforge-gdml/src/commonMain/kotlin/hep/dataforge/vision/gdml/GdmlOptimizer.kt delete mode 100644 visionforge-gdml/src/commonMain/kotlin/hep/dataforge/vision/gdml/GdmlVision.kt create mode 100644 visionforge-gdml/src/jsMain/kotlin/hep/dataforge/vision/gdml/gdmlJs.kt rename visionforge-gdml/src/jvmMain/kotlin/hep/dataforge/vision/gdml/{visualGDMLJvm.kt => gdmlJVM.kt} (85%) create mode 100644 visionforge-gdml/src/jvmTest/kotlin/hep/dataforge/vision/gdml/bmanStatistics.kt diff --git a/demo/gdml/build.gradle.kts b/demo/gdml/build.gradle.kts index c16fce83..8d5342c3 100644 --- a/demo/gdml/build.gradle.kts +++ b/demo/gdml/build.gradle.kts @@ -1,6 +1,5 @@ import scientifik.DependencyConfiguration import scientifik.FXModule -import scientifik.useFx plugins { id("scientifik.mpp") @@ -37,7 +36,7 @@ kotlin { } application { - mainClassName = "hep.dataforge.vision.gdml.demo.GDMLDemoAppKt" + mainClassName = "hep.dataforge.vision.gdml.demo.GdmlFxDemoAppKt" } val convertGdmlToJson by tasks.creating(JavaExec::class) { diff --git a/demo/gdml/src/jsMain/kotlin/hep/dataforge/vision/gdml/demo/GDMLAppComponent.kt b/demo/gdml/src/jsMain/kotlin/hep/dataforge/vision/gdml/demo/GDMLAppComponent.kt index f6d7d016..ae1dce60 100644 --- a/demo/gdml/src/jsMain/kotlin/hep/dataforge/vision/gdml/demo/GDMLAppComponent.kt +++ b/demo/gdml/src/jsMain/kotlin/hep/dataforge/vision/gdml/demo/GDMLAppComponent.kt @@ -1,6 +1,7 @@ package hep.dataforge.vision.gdml.demo import hep.dataforge.context.Context +import hep.dataforge.meta.set import hep.dataforge.names.Name import hep.dataforge.names.isEmpty import hep.dataforge.vision.Vision @@ -8,7 +9,6 @@ import hep.dataforge.vision.VisionGroup import hep.dataforge.vision.bootstrap.* import hep.dataforge.vision.gdml.toVision import hep.dataforge.vision.react.component -import hep.dataforge.vision.react.flexColumn import hep.dataforge.vision.react.objectTree import hep.dataforge.vision.react.state import hep.dataforge.vision.solid.Solid @@ -29,7 +29,6 @@ import react.dom.h1 import scientifik.gdml.GDML import scientifik.gdml.parse import styled.css -import styled.styledDiv import kotlin.browser.window import kotlin.math.PI @@ -50,14 +49,14 @@ private val canvasConfig = Canvas3DOptions { val GDMLApp = component { props -> var selected by state { props.selected } var canvas: ThreeCanvas? by state { null } - var visual: Vision? by state { props.rootObject } + var vision: Vision? by state { props.rootObject } val select: (Name?) -> Unit = { selected = it } fun loadData(name: String, data: String) { - visual = when { + val parsedVision = when { name.endsWith(".gdml") || name.endsWith(".xml") -> { val gdml = GDML.parse(data) gdml.toVision(gdmlConfiguration) @@ -68,20 +67,26 @@ val GDMLApp = component { props -> error("File extension is not recognized: $name") } } + parsedVision.config["edges.enabled"] = false + + vision = parsedVision } - flexColumn { + gridColumn { css { flex(1.0, 1.0, FlexBasis.auto) } h1 { +"GDML/JSON loader demo" } - styledDiv { + gridRow { css { - classes.add("row") - classes.add("p-1") + +"p-1" overflow = Overflow.auto } - gridColumn(3, maxSize = GridMaxSize.XL, classes = "order-2 order-xl-1") { + gridColumn(3, maxSize = GridMaxSize.XL) { + css { + +"order-2" + +"order-xl-1" + } card("Load data") { fileDrop("(drag file here)") { files -> val file = files?.get(0) @@ -99,15 +104,19 @@ val GDMLApp = component { props -> } //tree card("Object tree") { - visual?.let { + vision?.let { objectTree(it, selected, select) } } } - gridColumn(6, maxSize = GridMaxSize.XL, classes = "order-1 order-xl-2") { + gridColumn(6, maxSize = GridMaxSize.XL) { + css { + +"order-1" + +"order-xl-2" + } //canvas - (visual as? Solid)?.let { visual3D -> + (vision as? Solid)?.let { visual3D -> child(ThreeCanvasComponent::class) { attrs { this.context = props.context @@ -121,7 +130,10 @@ val GDMLApp = component { props -> } } } - gridColumn(3, maxSize = GridMaxSize.XL, classes = "order-3") { + gridColumn(3, maxSize = GridMaxSize.XL) { + css { + +"order-3" + } container { //settings canvas?.let { @@ -136,8 +148,8 @@ val GDMLApp = component { props -> selected.let { selected -> val selectedObject: Vision? = when { selected == null -> null - selected.isEmpty() -> visual - else -> (visual as? VisionGroup)?.get(selected) + selected.isEmpty() -> vision + else -> (vision as? VisionGroup)?.get(selected) } if (selectedObject != null) { visionPropertyEditor( diff --git a/demo/gdml/src/jsMain/kotlin/hep/dataforge/vision/gdml/demo/GDMLDemoApp.kt b/demo/gdml/src/jsMain/kotlin/hep/dataforge/vision/gdml/demo/GdmlJsDemoApp.kt similarity index 52% rename from demo/gdml/src/jsMain/kotlin/hep/dataforge/vision/gdml/demo/GDMLDemoApp.kt rename to demo/gdml/src/jsMain/kotlin/hep/dataforge/vision/gdml/demo/GdmlJsDemoApp.kt index 56b9fecd..40d35a09 100644 --- a/demo/gdml/src/jsMain/kotlin/hep/dataforge/vision/gdml/demo/GDMLDemoApp.kt +++ b/demo/gdml/src/jsMain/kotlin/hep/dataforge/vision/gdml/demo/GdmlJsDemoApp.kt @@ -16,23 +16,9 @@ import kotlin.browser.document val gdmlConfiguration: GDMLTransformer.() -> Unit = { lUnit = LUnit.CM - volumeAction = { volume -> - when { - volume.name.startsWith("ecal01lay") -> GDMLTransformer.Action.REJECT - volume.name.startsWith("UPBL") -> GDMLTransformer.Action.REJECT - volume.name.startsWith("USCL") -> GDMLTransformer.Action.REJECT - volume.name.startsWith("VPBL") -> GDMLTransformer.Action.REJECT - volume.name.startsWith("VSCL") -> GDMLTransformer.Action.REJECT - else -> GDMLTransformer.Action.CACHE - } - } - solidConfiguration = { parent, solid -> - if ( - solid.name.startsWith("Yoke") - || solid.name.startsWith("Pole") - || parent.physVolumes.isNotEmpty() - ) { + solidConfiguration = { parent, _ -> + if (parent.physVolumes.isNotEmpty()) { useStyle("opaque") { MATERIAL_OPACITY_KEY put 0.3 } @@ -64,19 +50,6 @@ private class GDMLDemoApp : Application { } } } -// (document.getElementById("file_load_button") as? HTMLInputElement)?.apply { -// addEventListener("change", { -// (it.target as HTMLInputElement).files?.asList()?.first()?.let { file -> -// FileReader().apply { -// onload = { -// val string = result as String -// action(file.name, string) -// } -// readAsText(file) -// } -// } -// }, false) -// } } } diff --git a/demo/gdml/src/jvmMain/kotlin/hep/dataforge/vision/gdml/demo/GDMLDemoApp.kt b/demo/gdml/src/jvmMain/kotlin/hep/dataforge/vision/gdml/demo/GdmlFxDemoApp.kt similarity index 86% rename from demo/gdml/src/jvmMain/kotlin/hep/dataforge/vision/gdml/demo/GDMLDemoApp.kt rename to demo/gdml/src/jvmMain/kotlin/hep/dataforge/vision/gdml/demo/GdmlFxDemoApp.kt index 35b00615..c69682b8 100644 --- a/demo/gdml/src/jvmMain/kotlin/hep/dataforge/vision/gdml/demo/GDMLDemoApp.kt +++ b/demo/gdml/src/jvmMain/kotlin/hep/dataforge/vision/gdml/demo/GdmlFxDemoApp.kt @@ -36,12 +36,11 @@ class GDMLView : View() { buttonbar { button("Load GDML/json") { action { - runAsync { - val file = chooseFile("Select a GDML/json file", filters = fileNameFilter).firstOrNull() - ?: return@runAsync null - SolidManager.readFile(file) - } ui { - if (it != null) { + val file = chooseFile("Select a GDML/json file", filters = fileNameFilter).firstOrNull() + if(file!= null) { + runAsync { + SolidManager.readFile(file) + } ui { canvas.render(it) } } diff --git a/demo/spatial-showcase/src/jsMain/kotlin/hep/dataforge/vision/solid/demo/VariableBox.kt b/demo/spatial-showcase/src/jsMain/kotlin/hep/dataforge/vision/solid/demo/VariableBox.kt index 3664d4a5..abe6097b 100644 --- a/demo/spatial-showcase/src/jsMain/kotlin/hep/dataforge/vision/solid/demo/VariableBox.kt +++ b/demo/spatial-showcase/src/jsMain/kotlin/hep/dataforge/vision/solid/demo/VariableBox.kt @@ -71,7 +71,7 @@ private object VariableBoxThreeFactory : ThreeFactory { //JS sometimes tries to pass Geometry as BufferGeometry @Suppress("USELESS_IS_CHECK") if (geometry !is BufferGeometry) error("BufferGeometry expected") - val mesh = Mesh(geometry, getMaterial(obj)).apply { + val mesh = Mesh(geometry, getMaterial(obj,true)).apply { applyEdges(obj) applyWireFrame(obj) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 62d4c053550b91381bbd28b1afc82d634bf73a8a..e708b1c023ec8b20f512888fe07c5bd3ff77bb8f 100644 GIT binary patch delta 6656 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 Yte!%o?r4GTrVtxD61Ym!|5fq-1K$0e!T1w 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index bb8b2fc2..6c9a2247 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.6-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index fbd7c515..4f906e0c 100644 --- a/gradlew +++ b/gradlew @@ -130,7 +130,7 @@ fi if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath diff --git a/gradlew.bat b/gradlew.bat index 5093609d..107acd32 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -54,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -64,21 +64,6 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line @@ -86,7 +71,7 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell diff --git a/ui/bootstrap/src/main/kotlin/hep/dataforge/vision/bootstrap/bootstrap.kt b/ui/bootstrap/src/main/kotlin/hep/dataforge/vision/bootstrap/bootstrap.kt index d624be23..f41c7ecc 100644 --- a/ui/bootstrap/src/main/kotlin/hep/dataforge/vision/bootstrap/bootstrap.kt +++ b/ui/bootstrap/src/main/kotlin/hep/dataforge/vision/bootstrap/bootstrap.kt @@ -13,6 +13,9 @@ import react.RBuilder import react.ReactElement import react.child import react.dom.* +import styled.StyledDOMBuilder +import styled.css +import styled.styledDiv inline fun TagConsumer.card(title: String, crossinline block: TagConsumer.() -> Unit) { div("card w-100") { @@ -180,10 +183,14 @@ enum class ContainerSize(val suffix: String) { } inline fun RBuilder.container( - classes: String? = null, size: ContainerSize = ContainerSize.FLUID, - block: RDOMBuilder
.() -> Unit -): ReactElement = div(joinStyles(classes, "container${size.suffix}"), block) + block: StyledDOMBuilder
.() -> Unit +): ReactElement = styledDiv{ + css{ + classes.add("container${size.suffix}") + } + block() +} enum class GridMaxSize(val suffix: String) { @@ -196,19 +203,23 @@ enum class GridMaxSize(val suffix: String) { inline fun RBuilder.gridColumn( weight: Int? = null, - classes: String? = null, maxSize: GridMaxSize = GridMaxSize.NONE, - block: RDOMBuilder
.() -> Unit -): ReactElement { + block: StyledDOMBuilder
.() -> Unit +): ReactElement = styledDiv { val weightSuffix = weight?.let { "-$it" } ?: "" - return div(joinStyles(classes, "col${maxSize.suffix}$weightSuffix"), block) + css { + classes.add("col${maxSize.suffix}$weightSuffix") + } + block() } inline fun RBuilder.gridRow( - classes: String? = null, - block: RDOMBuilder
.() -> Unit -): ReactElement { - return div(joinStyles(classes, "row"), block) + block: StyledDOMBuilder
.() -> Unit +): ReactElement = styledDiv{ + css{ + classes.add("row") + } + block() } fun Element.renderObjectTree( diff --git a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionGroup.kt b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionGroup.kt index cae2e2b0..43b964f2 100644 --- a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionGroup.kt +++ b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionGroup.kt @@ -91,8 +91,7 @@ interface MutableVisionGroup : VisionGroup { operator fun VisionGroup.get(str: String?): Vision? = get(str?.toName() ?: Name.EMPTY) -operator fun MutableVisionGroup.set(key: String, child: Vision?) { - set(key.toName(), child) -} +operator fun MutableVisionGroup.set(token: NameToken, child: Vision?): Unit = set(token.asName(), child) +operator fun MutableVisionGroup.set(key: String, child: Vision?): Unit = set(key.toName(), child) fun MutableVisionGroup.removeAll() = children.keys.map { it.asName() }.forEach { this[it] = null } \ No newline at end of file diff --git a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/visitor/StatisticsVisitor.kt b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/visitor/StatisticsVisitor.kt new file mode 100644 index 00000000..21842994 --- /dev/null +++ b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/visitor/StatisticsVisitor.kt @@ -0,0 +1,35 @@ +package hep.dataforge.vision.visitor + +import hep.dataforge.names.Name +import hep.dataforge.vision.Vision +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.Job +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlin.reflect.KClass + + +@OptIn(ExperimentalCoroutinesApi::class) +suspend fun Vision.flowStatistics(statistics: (Name, Vision) -> T): Flow = callbackFlow { + val visitor = object : VisionVisitor { + override suspend fun visit(name: Name, vision: Vision){ + send(statistics(name, vision)) + } + } + val job: Job = VisionVisitor.visitTree(visitor, this, this@flowStatistics) + job.invokeOnCompletion { + channel.close() + } + awaitClose { + job.cancel() + } +} + +data class DefaultVisionStatistics(val name: Name, val type: KClass) { + val depth get() = name.length +} + +suspend fun Vision.flowStatistics(): Flow = flowStatistics { name, vision -> + DefaultVisionStatistics(name, vision::class) +} \ No newline at end of file diff --git a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/visitor/VisionVisitor.kt b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/visitor/VisionVisitor.kt new file mode 100644 index 00000000..192e36a0 --- /dev/null +++ b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/visitor/VisionVisitor.kt @@ -0,0 +1,55 @@ +package hep.dataforge.vision.visitor + +import hep.dataforge.names.Name +import hep.dataforge.names.plus +import hep.dataforge.vision.Vision +import hep.dataforge.vision.VisionGroup +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch + +interface VisionVisitor { + /** + * Visit a vision possibly mutating in in the process. Should not rearrange children. + * @param name full name of a [Vision] being visited + * @param vision the visited [Vision] + */ + suspend fun visit(name: Name, vision: Vision) + + /** + * Rearrange children of given group + */ + suspend fun visitChildren(name: Name, group: VisionGroup) { + //Do nothing by default + } + + fun skip(name: Name, vision: Vision): Boolean = false + + companion object{ + private fun CoroutineScope.visitTreeAsync( + visionVisitor: VisionVisitor, + name: Name, + 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) + } + } + } + + /** + * Recursively visit this [Vision] and all children + */ + fun visitTree(visionVisitor: VisionVisitor, scope: CoroutineScope, root: Vision): Job = + scope.visitTreeAsync(visionVisitor, Name.EMPTY, root) + + + } +} + diff --git a/visionforge-core/src/jvmMain/kotlin/hep/dataforge/vision/visitor/countDistinct.kt b/visionforge-core/src/jvmMain/kotlin/hep/dataforge/vision/visitor/countDistinct.kt new file mode 100644 index 00000000..6f9594de --- /dev/null +++ b/visionforge-core/src/jvmMain/kotlin/hep/dataforge/vision/visitor/countDistinct.kt @@ -0,0 +1,16 @@ +package hep.dataforge.vision.visitor + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.collect +import java.util.concurrent.atomic.AtomicInteger + +suspend fun Flow.countDistinctBy(selector: (T) -> K): Map { + val counter = LinkedHashMap() + collect { + val key = selector(it) + counter.getOrPut(key) { AtomicInteger() }.incrementAndGet() + } + return counter.mapValues { it.value.toInt() } +} + +suspend fun Flow.countDistinct(): Map = countDistinctBy { it } \ No newline at end of file diff --git a/visionforge-gdml/src/commonMain/kotlin/hep/dataforge/vision/gdml/GDMLTransformer.kt b/visionforge-gdml/src/commonMain/kotlin/hep/dataforge/vision/gdml/GDMLTransformer.kt index b7019f69..82413d93 100644 --- a/visionforge-gdml/src/commonMain/kotlin/hep/dataforge/vision/gdml/GDMLTransformer.kt +++ b/visionforge-gdml/src/commonMain/kotlin/hep/dataforge/vision/gdml/GDMLTransformer.kt @@ -4,11 +4,16 @@ import hep.dataforge.meta.Meta import hep.dataforge.meta.MetaBuilder import hep.dataforge.names.Name import hep.dataforge.names.asName +import hep.dataforge.names.plus import hep.dataforge.names.toName +import hep.dataforge.vision.get +import hep.dataforge.vision.set import hep.dataforge.vision.solid.* import hep.dataforge.vision.solid.SolidMaterial.Companion.MATERIAL_COLOR_KEY import hep.dataforge.vision.useStyle import scientifik.gdml.* +import kotlin.math.cos +import kotlin.math.sin import kotlin.random.Random class GDMLTransformer(val root: GDML) { @@ -21,17 +26,16 @@ class GDMLTransformer(val root: GDML) { CACHE } - /** - * A special group for local templates - */ - val proto by lazy { SolidGroup() } - private val styleCache = HashMap() - var lUnit: LUnit = LUnit.MM var solidAction: (GDMLSolid) -> Action = { Action.CACHE } var volumeAction: (GDMLGroup) -> Action = { Action.CACHE } + /** + * A special group for local templates + */ + internal val proto by lazy { SolidGroup() } + private val styleCache = HashMap() var solidConfiguration: Solid.(parent: GDMLVolume, solid: GDMLSolid) -> Unit = { parent, _ -> lUnit = LUnit.CM @@ -86,4 +90,275 @@ class GDMLTransformer(val root: GDML) { return final } +} + +private fun Solid.withPosition( + lUnit: LUnit, + newPos: GDMLPosition? = null, + newRotation: GDMLRotation? = null, + newScale: GDMLScale? = null +): Solid = apply { + newPos?.let { + val point = Point3D(it.x(lUnit), it.y(lUnit), it.z(lUnit)) + if (position != null || point != World.ZERO) { + position = point + } + } + newRotation?.let { + val point = Point3D(it.x(), it.y(), it.z()) + if (rotation != null || point != World.ZERO) { + rotation = point + } + //this@withPosition.rotationOrder = RotationOrder.ZXY + } + newScale?.let { + val point = Point3D(it.x, it.y, it.z) + if (scale != null || point != World.ONE) { + scale = point + } + } + //TODO convert units if needed +} + +@Suppress("NOTHING_TO_INLINE") +private inline operator fun Number.times(d: Double) = toDouble() * d + +@Suppress("NOTHING_TO_INLINE") +private inline operator fun Number.times(f: Float) = toFloat() * f + +private fun SolidGroup.addSolid( + context: GDMLTransformer, + solid: GDMLSolid, + name: String = "", + block: Solid.() -> Unit = {} +): Solid { + //context.solidAdded(solid) + val lScale = solid.lscale(context.lUnit) + val aScale = solid.ascale() + return when (solid) { + is GDMLBox -> box(solid.x * lScale, solid.y * lScale, solid.z * lScale, name) + is GDMLTube -> tube( + solid.rmax * lScale, + solid.z * lScale, + solid.rmin * lScale, + solid.startphi * aScale, + solid.deltaphi * aScale, + name + ) + is GDMLCone -> cone(solid.rmax1, solid.z, solid.rmax2, name = name) { + require(solid.rmin1 == 0.0) { "Empty cones are not supported" } + require(solid.rmin2 == 0.0) { "Empty cones are not supported" } + startAngle = solid.startphi.toFloat() + angle = solid.deltaphi.toFloat() + } + is GDMLXtru -> extrude(name) { + shape { + solid.vertices.forEach { + point(it.x * lScale, it.y * lScale) + } + } + solid.sections.sortedBy { it.zOrder }.forEach { section -> + layer( + section.zPosition * lScale, + section.xOffset * lScale, + section.yOffset * lScale, + section.scalingFactor + ) + } + } + is GDMLScaledSolid -> { + //Add solid with modified scale + val innerSolid = solid.solidref.resolve(context.root) + ?: error("Solid with tag ${solid.solidref.ref} for scaled solid ${solid.name} not defined") + + addSolid(context, innerSolid, name) { + block() + scaleX *= solid.scale.x.toFloat() + scaleY *= solid.scale.y.toFloat() + scaleZ = solid.scale.z.toFloat() + } + } + is GDMLSphere -> sphere(solid.rmax * lScale, solid.deltaphi * aScale, solid.deltatheta * aScale, name) { + phiStart = solid.startphi * aScale + thetaStart = solid.starttheta * aScale + } + is GDMLOrb -> sphere(solid.r * lScale, name = name) + is GDMLPolyhedra -> extrude(name) { + //getting the radius of first + require(solid.planes.size > 1) { "The polyhedron geometry requires at least two planes" } + val baseRadius = solid.planes.first().rmax * lScale + shape { + (0..solid.numsides).forEach { + val phi = solid.deltaphi * aScale / solid.numsides * it + solid.startphi * aScale + (baseRadius * cos(phi) to baseRadius * sin(phi)) + } + } + solid.planes.forEach { plane -> + //scaling all radii relative to first layer radius + layer(plane.z * lScale, scale = plane.rmax * lScale / baseRadius) + } + } + is GDMLBoolSolid -> { + val first = solid.first.resolve(context.root) ?: error("") + val second = solid.second.resolve(context.root) ?: error("") + val type: CompositeType = when (solid) { + is GDMLUnion -> CompositeType.UNION + is GDMLSubtraction -> CompositeType.SUBTRACT + is GDMLIntersection -> CompositeType.INTERSECT + } + + return composite(type, name) { + addSolid(context, first) { + withPosition( + context.lUnit, + solid.resolveFirstPosition(context.root), + solid.resolveFirstRotation(context.root), + null + ) + } + addSolid(context, second) { + withPosition( + context.lUnit, + solid.resolvePosition(context.root), + solid.resolveRotation(context.root), + null + ) + } + } + } + else -> error("Renderer for $solid not supported yet") + }.apply(block) +} + + +private fun SolidGroup.addSolidWithCaching( + context: GDMLTransformer, + solid: GDMLSolid, + volume: GDMLVolume, + name: String = solid.name +) { + when (context.solidAction(solid)) { + GDMLTransformer.Action.ACCEPT -> { + addSolid(context, solid, name) { + context.configureSolid(this, volume, solid) + } + } + GDMLTransformer.Action.CACHE -> { + if (context.proto[solid.name] == null) { + context.proto.addSolid(context, solid, name) { + context.configureSolid(this, volume, solid) + } + } + ref(solid.name.asName(), name) + } + GDMLTransformer.Action.REJECT -> { + //ignore + } + } + +} + +private val volumesName = "volumes".asName() + +private fun SolidGroup.addPhysicalVolume( + context: GDMLTransformer, + physVolume: GDMLPhysVolume +) { + val volume: GDMLGroup = physVolume.volumeref.resolve(context.root) + ?: error("Volume with ref ${physVolume.volumeref.ref} could not be resolved") + + // a special case for single solid volume +// if (volume is GDMLVolume && volume.physVolumes.isEmpty() && volume.placement == null) { +// val solid = volume.solidref.resolve(context.root) +// ?: error("Solid with tag ${volume.solidref.ref} for volume ${volume.name} not defined") +// addSolidWithCaching(context, solid, volume, physVolume.name ?: "").apply { +// withPosition( +// context.lUnit, +// physVolume.resolvePosition(context.root), +// physVolume.resolveRotation(context.root), +// physVolume.resolveScale(context.root) +// ) +// } +// return +// } + + when (context.volumeAction(volume)) { + GDMLTransformer.Action.ACCEPT -> { + val group: SolidGroup = volume(context, volume) + this[physVolume.name ?: ""] = group.apply { + withPosition( + context.lUnit, + physVolume.resolvePosition(context.root), + physVolume.resolveRotation(context.root), + physVolume.resolveScale(context.root) + ) + } + } + GDMLTransformer.Action.CACHE -> { + val fullName = volumesName + volume.name.asName() + if (context.proto[fullName] == null) { + context.proto[fullName] = volume(context, volume) + } + + this[physVolume.name ?: ""] = Proxy(this, fullName).apply { + withPosition( + context.lUnit, + physVolume.resolvePosition(context.root), + physVolume.resolveRotation(context.root), + physVolume.resolveScale(context.root) + ) + } + } + GDMLTransformer.Action.REJECT -> { + //ignore + } + } +} + +private fun SolidGroup.addDivisionVolume( + context: GDMLTransformer, + divisionVolume: GDMLDivisionVolume +) { + val volume: GDMLGroup = divisionVolume.volumeref.resolve(context.root) + ?: error("Volume with ref ${divisionVolume.volumeref.ref} could not be resolved") + + //TODO add divisions + set(Name.EMPTY, volume(context, volume)) +} + +//private val solidsName = "solids".asName() + +private fun volume( + context: GDMLTransformer, + group: GDMLGroup +): SolidGroup = SolidGroup().apply { + if (group is GDMLVolume) { + val solid: GDMLSolid = group.solidref.resolve(context.root) + ?: error("Solid with tag ${group.solidref.ref} for volume ${group.name} not defined") + + addSolidWithCaching(context, solid, group) + + when (val vol: GDMLPlacement? = group.placement) { + is GDMLPhysVolume -> addPhysicalVolume(context, vol) + is GDMLDivisionVolume -> addDivisionVolume(context, vol) + } + } + + group.physVolumes.forEach { physVolume -> + addPhysicalVolume(context, physVolume) + } +} + +fun GDML.toVision(block: GDMLTransformer.() -> Unit = {}): SolidGroup { + val context = GDMLTransformer(this).apply(block) + return context.finalize(volume(context, world)) +} + +/** + * Append gdml node to the group + */ +fun SolidGroup.gdml(gdml: GDML, key: String = "", transformer: GDMLTransformer.() -> Unit = {}) { + val visual = gdml.toVision(transformer) + //println(Visual3DPlugin.json.stringify(VisualGroup3D.serializer(), visual)) + set(key, visual) } \ No newline at end of file diff --git a/visionforge-gdml/src/commonMain/kotlin/hep/dataforge/vision/gdml/GdmlOptimizer.kt b/visionforge-gdml/src/commonMain/kotlin/hep/dataforge/vision/gdml/GdmlOptimizer.kt new file mode 100644 index 00000000..c138fcc6 --- /dev/null +++ b/visionforge-gdml/src/commonMain/kotlin/hep/dataforge/vision/gdml/GdmlOptimizer.kt @@ -0,0 +1,89 @@ +package hep.dataforge.vision.gdml + +import hep.dataforge.meta.DFExperimental +import hep.dataforge.meta.sequence +import hep.dataforge.meta.set +import hep.dataforge.names.Name +import hep.dataforge.names.toName +import hep.dataforge.vision.* +import hep.dataforge.vision.solid.* +import hep.dataforge.vision.visitor.VisionVisitor +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.Job +import kotlinx.coroutines.coroutineScope +import mu.KotlinLogging + +expect class Counter() { + fun get(): Int + fun incrementAndGet(): Int +} + +@DFExperimental +private class GdmlOptimizer() : VisionVisitor { + val logger = KotlinLogging.logger("SingleChildReducer") + + private operator fun Point3D?.plus(other: Point3D?): Point3D? = if (this == null && other == null) { + null + } else { + (this ?: Point3D(0, 0, 0)) + (other ?: Point3D(0, 0, 0)) + } + + private fun Vision.updateFrom(other: Vision): Vision { + if (this is Solid && other is Solid) { + position += other.position + rotation += other.rotation + if (this.scale != null || other.scale != null) { + scaleX = scaleX.toDouble() * other.scaleX.toDouble() + scaleY = scaleY.toDouble() * other.scaleY.toDouble() + scaleZ = scaleZ.toDouble() * other.scaleZ.toDouble() + } + other.properties?.sequence()?.forEach { (name, item) -> + if (properties?.getItem(name) == null) { + config[name] = item + } + } + } + return this + } + + private val depthCount = HashMap() + + override suspend fun visit(name: Name, vision: Vision) { + val depth = name.length + depthCount.getOrPut(depth) { Counter() }.incrementAndGet() + } + + override fun skip(name: Name, vision: Vision): Boolean = vision is Proxy.ProxyChild + + override suspend fun visitChildren(name: Name, group: VisionGroup) { + if (name == "volumes".toName()) return + if (group !is MutableVisionGroup) return + + val newChildren = group.children.entries.associate { (visionToken, vision) -> + //Reduce single child groups + if (vision is VisionGroup && vision !is Proxy && vision.children.size == 1) { + val (token, child) = vision.children.entries.first() + child.parent = null + if (token != visionToken) { + child.config["solidName"] = token.toString() + } + visionToken to child.updateFrom(vision) + } else { + visionToken to vision + } + } + if (newChildren != group.children) { + group.removeAll() + newChildren.forEach { (token, child) -> + group[token] = child + } + } + } +} + +@DFExperimental +suspend fun SolidGroup.optimizeGdml(): Job = coroutineScope { + prototypes?.let { + VisionVisitor.visitTree(GdmlOptimizer(), this, it) + } ?: CompletableDeferred(Unit) +} \ No newline at end of file diff --git a/visionforge-gdml/src/commonMain/kotlin/hep/dataforge/vision/gdml/GdmlVision.kt b/visionforge-gdml/src/commonMain/kotlin/hep/dataforge/vision/gdml/GdmlVision.kt deleted file mode 100644 index 29efa338..00000000 --- a/visionforge-gdml/src/commonMain/kotlin/hep/dataforge/vision/gdml/GdmlVision.kt +++ /dev/null @@ -1,268 +0,0 @@ -package hep.dataforge.vision.gdml - - -import hep.dataforge.names.Name -import hep.dataforge.names.asName -import hep.dataforge.names.plus -import hep.dataforge.vision.get -import hep.dataforge.vision.set -import hep.dataforge.vision.solid.* -import hep.dataforge.vision.solid.World.ONE -import hep.dataforge.vision.solid.World.ZERO -import scientifik.gdml.* -import kotlin.math.cos -import kotlin.math.sin - - -private fun Solid.withPosition( - lUnit: LUnit, - newPos: GDMLPosition? = null, - newRotation: GDMLRotation? = null, - newScale: GDMLScale? = null -): Solid = apply { - newPos?.let { - val point = Point3D(it.x(lUnit), it.y(lUnit), it.z(lUnit)) - if (position != null || point != ZERO) { - position = point - } - } - newRotation?.let { - val point = Point3D(it.x(), it.y(), it.z()) - if (rotation != null || point != ZERO) { - rotation = point - } - //this@withPosition.rotationOrder = RotationOrder.ZXY - } - newScale?.let { - val point = Point3D(it.x, it.y, it.z) - if (scale != null || point != ONE) { - scale = point - } - } - //TODO convert units if needed -} - -@Suppress("NOTHING_TO_INLINE") -private inline operator fun Number.times(d: Double) = toDouble() * d - -@Suppress("NOTHING_TO_INLINE") -private inline operator fun Number.times(f: Float) = toFloat() * f - -private fun SolidGroup.addSolid( - context: GDMLTransformer, - solid: GDMLSolid, - name: String = "", - block: Solid.() -> Unit = {} -): Solid { - //context.solidAdded(solid) - val lScale = solid.lscale(context.lUnit) - val aScale = solid.ascale() - return when (solid) { - is GDMLBox -> box(solid.x * lScale, solid.y * lScale, solid.z * lScale, name) - is GDMLTube -> tube( - solid.rmax * lScale, - solid.z * lScale, - solid.rmin * lScale, - solid.startphi * aScale, - solid.deltaphi * aScale, - name - ) - is GDMLCone -> cone(solid.rmax1, solid.z, solid.rmax2, name = name) { - require(solid.rmin1 == 0.0) { "Empty cones are not supported" } - require(solid.rmin2 == 0.0) { "Empty cones are not supported" } - startAngle = solid.startphi.toFloat() - angle = solid.deltaphi.toFloat() - } - is GDMLXtru -> extrude(name) { - shape { - solid.vertices.forEach { - point(it.x * lScale, it.y * lScale) - } - } - solid.sections.sortedBy { it.zOrder }.forEach { section -> - layer( - section.zPosition * lScale, - section.xOffset * lScale, - section.yOffset * lScale, - section.scalingFactor - ) - } - } - is GDMLScaledSolid -> { - //Add solid with modified scale - val innerSolid = solid.solidref.resolve(context.root) - ?: error("Solid with tag ${solid.solidref.ref} for scaled solid ${solid.name} not defined") - - addSolid(context, innerSolid, name) { - block() - scaleX *= solid.scale.x.toFloat() - scaleY *= solid.scale.y.toFloat() - scaleZ = solid.scale.z.toFloat() - } - } - is GDMLSphere -> sphere(solid.rmax * lScale, solid.deltaphi * aScale, solid.deltatheta * aScale, name) { - phiStart = solid.startphi * aScale - thetaStart = solid.starttheta * aScale - } - is GDMLOrb -> sphere(solid.r * lScale, name = name) - is GDMLPolyhedra -> extrude(name) { - //getting the radius of first - require(solid.planes.size > 1) { "The polyhedron geometry requires at least two planes" } - val baseRadius = solid.planes.first().rmax * lScale - shape { - (0..solid.numsides).forEach { - val phi = solid.deltaphi * aScale / solid.numsides * it + solid.startphi * aScale - (baseRadius * cos(phi) to baseRadius * sin(phi)) - } - } - solid.planes.forEach { plane -> - //scaling all radii relative to first layer radius - layer(plane.z * lScale, scale = plane.rmax * lScale / baseRadius) - } - } - is GDMLBoolSolid -> { - val first = solid.first.resolve(context.root) ?: error("") - val second = solid.second.resolve(context.root) ?: error("") - val type: CompositeType = when (solid) { - is GDMLUnion -> CompositeType.UNION - is GDMLSubtraction -> CompositeType.SUBTRACT - is GDMLIntersection -> CompositeType.INTERSECT - } - - return composite(type, name) { - addSolid(context, first) { - withPosition( - context.lUnit, - solid.resolveFirstPosition(context.root), - solid.resolveFirstRotation(context.root), - null - ) - } - addSolid(context, second) { - withPosition( - context.lUnit, - solid.resolvePosition(context.root), - solid.resolveRotation(context.root), - null - ) - } - } - } - else -> error("Renderer for $solid not supported yet") - }.apply(block) -} - -private val volumesName = "volumes".asName() - -private fun SolidGroup.addPhysicalVolume( - context: GDMLTransformer, - physVolume: GDMLPhysVolume -) { - val volume: GDMLGroup = physVolume.volumeref.resolve(context.root) - ?: error("Volume with ref ${physVolume.volumeref.ref} could not be resolved") - - when (context.volumeAction(volume)) { - GDMLTransformer.Action.ACCEPT -> { - val group = volume(context, volume) - this[physVolume.name ?: ""] = group.apply { - withPosition( - context.lUnit, - physVolume.resolvePosition(context.root), - physVolume.resolveRotation(context.root), - physVolume.resolveScale(context.root) - ) - } - } - GDMLTransformer.Action.CACHE -> { - val fullName = volumesName + volume.name.asName() - if (context.proto[fullName] == null) { - context.proto[fullName] = volume(context, volume) - } - - this[physVolume.name ?: ""] = Proxy(this, fullName).apply { - withPosition( - context.lUnit, - physVolume.resolvePosition(context.root), - physVolume.resolveRotation(context.root), - physVolume.resolveScale(context.root) - ) - } - } - GDMLTransformer.Action.REJECT -> { - //ignore - } - } -} - -private fun SolidGroup.addDivisionVolume( - context: GDMLTransformer, - divisionVolume: GDMLDivisionVolume -) { - val volume: GDMLGroup = divisionVolume.volumeref.resolve(context.root) - ?: error("Volume with ref ${divisionVolume.volumeref.ref} could not be resolved") - - //TODO add divisions - set( - Name.EMPTY, - volume( - context, - volume - ) - ) -} - -private val solidsName = "solids".asName() - -private fun volume( - context: GDMLTransformer, - group: GDMLGroup -): SolidGroup { - return SolidGroup().apply { - if (group is GDMLVolume) { - val solid = group.solidref.resolve(context.root) - ?: error("Solid with tag ${group.solidref.ref} for volume ${group.name} not defined") - - when (context.solidAction(solid)) { - GDMLTransformer.Action.ACCEPT -> { - addSolid(context, solid, solid.name) { - context.configureSolid(this, group, solid) - } - } - GDMLTransformer.Action.CACHE -> { - if (context.proto[solid.name] == null) { - context.proto.addSolid(context, solid, solid.name) { - context.configureSolid(this, group, solid) - } - } - ref(solid.name.asName(), solid.name) - } - GDMLTransformer.Action.REJECT -> { - //ignore - } - } - - when (val vol = group.placement) { - is GDMLPhysVolume -> addPhysicalVolume(context, vol) - is GDMLDivisionVolume -> addDivisionVolume(context, vol) - } - } - - group.physVolumes.forEach { physVolume -> - addPhysicalVolume(context, physVolume) - } - } -} - -fun GDML.toVision(block: GDMLTransformer.() -> Unit = {}): SolidGroup { - val context = GDMLTransformer(this).apply(block) - return context.finalize(volume(context, world)) -} - -/** - * Append gdml node to the group - */ -fun SolidGroup.gdml(gdml: GDML, key: String = "", transformer: GDMLTransformer.() -> Unit = {}) { - val visual = gdml.toVision(transformer) - //println(Visual3DPlugin.json.stringify(VisualGroup3D.serializer(), visual)) - set(key, visual) -} \ No newline at end of file diff --git a/visionforge-gdml/src/jsMain/kotlin/hep/dataforge/vision/gdml/gdmlJs.kt b/visionforge-gdml/src/jsMain/kotlin/hep/dataforge/vision/gdml/gdmlJs.kt new file mode 100644 index 00000000..44a36b6c --- /dev/null +++ b/visionforge-gdml/src/jsMain/kotlin/hep/dataforge/vision/gdml/gdmlJs.kt @@ -0,0 +1,8 @@ +package hep.dataforge.vision.gdml + +actual class Counter { + private var count: Int = 0 + actual fun get(): Int = count + + actual fun incrementAndGet(): Int = count++ +} \ No newline at end of file diff --git a/visionforge-gdml/src/jvmMain/kotlin/hep/dataforge/vision/gdml/visualGDMLJvm.kt b/visionforge-gdml/src/jvmMain/kotlin/hep/dataforge/vision/gdml/gdmlJVM.kt similarity index 85% rename from visionforge-gdml/src/jvmMain/kotlin/hep/dataforge/vision/gdml/visualGDMLJvm.kt rename to visionforge-gdml/src/jvmMain/kotlin/hep/dataforge/vision/gdml/gdmlJVM.kt index 0d47ea7a..1718cc74 100644 --- a/visionforge-gdml/src/jvmMain/kotlin/hep/dataforge/vision/gdml/visualGDMLJvm.kt +++ b/visionforge-gdml/src/jvmMain/kotlin/hep/dataforge/vision/gdml/gdmlJVM.kt @@ -5,6 +5,9 @@ import nl.adaptivity.xmlutil.StAXReader import scientifik.gdml.GDML import java.nio.file.Files import java.nio.file.Path +import java.util.concurrent.atomic.AtomicInteger + +actual typealias Counter = AtomicInteger fun GDML.Companion.readFile(file: Path): GDML { val xmlReader = StAXReader(Files.newInputStream(file), "UTF-8") @@ -15,4 +18,3 @@ fun SolidGroup.gdml(file: Path, key: String = "", transformer: GDMLTransformer.( val gdml = GDML.readFile(file) gdml(gdml, key, transformer) } - diff --git a/visionforge-gdml/src/jvmTest/kotlin/hep/dataforge/vision/gdml/TestConvertor.kt b/visionforge-gdml/src/jvmTest/kotlin/hep/dataforge/vision/gdml/TestConvertor.kt index d932ab0e..287553a1 100644 --- a/visionforge-gdml/src/jvmTest/kotlin/hep/dataforge/vision/gdml/TestConvertor.kt +++ b/visionforge-gdml/src/jvmTest/kotlin/hep/dataforge/vision/gdml/TestConvertor.kt @@ -10,11 +10,10 @@ class TestConvertor { @Test fun testBMNGeometry() { val stream = javaClass.getResourceAsStream("/gdml/BM@N.gdml") - val xmlReader = StAXReader(stream, "UTF-8") val xml = GDML.format.parse(GDML.serializer(), xmlReader) - val visual = xml.toVision() - println(visual.stringify()) + val vision = xml.toVision() + println(vision.stringify()) } @Test diff --git a/visionforge-gdml/src/jvmTest/kotlin/hep/dataforge/vision/gdml/bmanStatistics.kt b/visionforge-gdml/src/jvmTest/kotlin/hep/dataforge/vision/gdml/bmanStatistics.kt new file mode 100644 index 00000000..a8af85c6 --- /dev/null +++ b/visionforge-gdml/src/jvmTest/kotlin/hep/dataforge/vision/gdml/bmanStatistics.kt @@ -0,0 +1,34 @@ +package hep.dataforge.vision.gdml + +import hep.dataforge.vision.visitor.countDistinctBy +import hep.dataforge.vision.visitor.flowStatistics +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import nl.adaptivity.xmlutil.StAXReader +import scientifik.gdml.GDML +import java.io.File + +suspend fun main() { + withContext(Dispatchers.Default) { + //val stream = SingleChildReducer::class.java.getResourceAsStream("/gdml/BM@N.gdml") + val stream = + File("D:\\Work\\Projects\\dataforge-vis\\visionforge-gdml\\src\\jvmTest\\resources\\gdml\\BM@N.gdml").inputStream() + + val xmlReader = StAXReader(stream, "UTF-8") + val xml = GDML.format.parse(GDML.serializer(), xmlReader) + val vision = xml.toVision() + + + vision.flowStatistics().countDistinctBy { it.type }.forEach { (depth, size) -> + println("$depth\t$size") + } + + println("***REDUCED***") + + vision.optimizeGdml() + + vision.flowStatistics().countDistinctBy { it.type }.forEach { (depth, size) -> + println("$depth\t$size") + } + } +} \ No newline at end of file diff --git a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Proxy.kt b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Proxy.kt index 53d65b0a..85d122c8 100644 --- a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Proxy.kt +++ b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Proxy.kt @@ -13,6 +13,9 @@ import kotlinx.serialization.Transient import kotlinx.serialization.UseSerializers import kotlin.collections.set + +class AbstractProxy + /** * A proxy [Solid] to reuse a template object */ @@ -39,8 +42,7 @@ class Proxy private constructor( get() = (parent as? SolidGroup)?.getPrototype(templateName) ?: error("Prototype with name $templateName not found in $parent") - override val styleSheet: StyleSheet - get() = parent?.styleSheet ?: StyleSheet(this) + override val styleSheet: StyleSheet get() = parent?.styleSheet ?: StyleSheet(this) override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? { return if (inherit) { diff --git a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Solid.kt b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Solid.kt index d1cfdaca..721d38ee 100644 --- a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Solid.kt +++ b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Solid.kt @@ -103,9 +103,9 @@ interface Solid : Vision { * Count number of layers to the top object. Return 1 if this is top layer */ var Solid.layer: Int - get() = getItem(LAYER_KEY).int ?: 0 + get() = properties?.getItem(LAYER_KEY).int ?: 0 set(value) { - setItem(LAYER_KEY, value.asValue()) + config[LAYER_KEY] = value.asValue() } fun Renderer.render(meta: Meta = Meta.EMPTY, action: SolidGroup.() -> Unit) = diff --git a/visionforge-solid/src/jsMain/kotlin/hep/dataforge/vision/solid/three/MeshThreeFactory.kt b/visionforge-solid/src/jsMain/kotlin/hep/dataforge/vision/solid/three/MeshThreeFactory.kt index f3b2d30f..eb7192c0 100644 --- a/visionforge-solid/src/jsMain/kotlin/hep/dataforge/vision/solid/three/MeshThreeFactory.kt +++ b/visionforge-solid/src/jsMain/kotlin/hep/dataforge/vision/solid/three/MeshThreeFactory.kt @@ -35,7 +35,7 @@ abstract class MeshThreeFactory( //val meshMeta: Meta = obj.properties[Material3D.MATERIAL_KEY]?.node ?: Meta.empty - val mesh = Mesh(geometry, getMaterial(obj)).apply { + val mesh = Mesh(geometry, getMaterial(obj, true)).apply { matrixAutoUpdate = false applyEdges(obj) applyWireFrame(obj) @@ -50,7 +50,7 @@ abstract class MeshThreeFactory( } //add listener to object properties - obj.onPropertyChange(this) { name-> + obj.onPropertyChange(this) { name -> when { name.startsWith(Solid.GEOMETRY_KEY) -> { val oldGeometry = mesh.geometry as BufferGeometry @@ -80,22 +80,28 @@ abstract class MeshThreeFactory( } fun Mesh.applyEdges(obj: Solid) { - children.find { it.name == "@edges" }?.let { - remove(it) - (it as LineSegments).dispose() - } + val edges = children.find { it.name == "@edges" } as? LineSegments //inherited edges definition, enabled by default if (obj.getItem(MeshThreeFactory.EDGES_ENABLED_KEY).boolean != false) { - - val material = ThreeMaterials.getLineMaterial(obj.getItem(MeshThreeFactory.EDGES_MATERIAL_KEY).node) - add( - LineSegments( - EdgesGeometry(geometry as BufferGeometry), - material - ).apply { - name = "@edges" - } - ) + val bufferGeometry = geometry as? BufferGeometry ?: return + val material = ThreeMaterials.getLineMaterial(obj.getItem(MeshThreeFactory.EDGES_MATERIAL_KEY).node, true) + if (edges == null) { + add( + LineSegments( + EdgesGeometry(bufferGeometry), + material + ).apply { + name = "@edges" + } + ) + } else { + edges.material = material + } + } else { + edges?.let { + remove(it) + it.dispose() + } } } @@ -106,10 +112,11 @@ fun Mesh.applyWireFrame(obj: Solid) { } //inherited wireframe definition, disabled by default if (obj.getItem(MeshThreeFactory.WIREFRAME_ENABLED_KEY).boolean == true) { - val material = ThreeMaterials.getLineMaterial(obj.getItem(MeshThreeFactory.WIREFRAME_MATERIAL_KEY).node) + val bufferGeometry = geometry as? BufferGeometry ?: return + val material = ThreeMaterials.getLineMaterial(obj.getItem(MeshThreeFactory.WIREFRAME_MATERIAL_KEY).node, true) add( LineSegments( - WireframeGeometry(geometry as BufferGeometry), + WireframeGeometry(bufferGeometry), material ).apply { name = "@wireframe" diff --git a/visionforge-solid/src/jsMain/kotlin/hep/dataforge/vision/solid/three/ThreeFactory.kt b/visionforge-solid/src/jsMain/kotlin/hep/dataforge/vision/solid/three/ThreeFactory.kt index 8bbd55a3..8b83ef16 100644 --- a/visionforge-solid/src/jsMain/kotlin/hep/dataforge/vision/solid/three/ThreeFactory.kt +++ b/visionforge-solid/src/jsMain/kotlin/hep/dataforge/vision/solid/three/ThreeFactory.kt @@ -58,7 +58,7 @@ fun Object3D.updatePosition(obj: Vision) { */ fun Object3D.updateProperty(source: Vision, propertyName: Name) { if (this is Mesh && propertyName.startsWith(MATERIAL_KEY)) { - this.material = getMaterial(source) + this.material = getMaterial(source, false) } else if ( propertyName.startsWith(Solid.POSITION_KEY) || propertyName.startsWith(Solid.ROTATION) diff --git a/visionforge-solid/src/jsMain/kotlin/hep/dataforge/vision/solid/three/ThreeLabelFactory.kt b/visionforge-solid/src/jsMain/kotlin/hep/dataforge/vision/solid/three/ThreeLabelFactory.kt index 60b0352a..f6c677c4 100644 --- a/visionforge-solid/src/jsMain/kotlin/hep/dataforge/vision/solid/three/ThreeLabelFactory.kt +++ b/visionforge-solid/src/jsMain/kotlin/hep/dataforge/vision/solid/three/ThreeLabelFactory.kt @@ -21,7 +21,7 @@ object ThreeLabelFactory : ThreeFactory { height = 1 curveSegments = 1 }) - return Mesh(textGeo, getMaterial(obj)).apply { + return Mesh(textGeo, getMaterial(obj,true)).apply { updatePosition(obj) obj.onPropertyChange(this@ThreeLabelFactory) { _ -> //TODO diff --git a/visionforge-solid/src/jsMain/kotlin/hep/dataforge/vision/solid/three/ThreeLineFactory.kt b/visionforge-solid/src/jsMain/kotlin/hep/dataforge/vision/solid/three/ThreeLineFactory.kt index c0b67e25..a032f527 100644 --- a/visionforge-solid/src/jsMain/kotlin/hep/dataforge/vision/solid/three/ThreeLineFactory.kt +++ b/visionforge-solid/src/jsMain/kotlin/hep/dataforge/vision/solid/three/ThreeLineFactory.kt @@ -18,7 +18,7 @@ object ThreeLineFactory : ThreeFactory { vertices = obj.points.toTypedArray() } - val material = ThreeMaterials.getLineMaterial(obj.getItem(MeshThreeFactory.EDGES_MATERIAL_KEY).node) + val material = ThreeMaterials.getLineMaterial(obj.getItem(MeshThreeFactory.EDGES_MATERIAL_KEY).node, true) material.linewidth = obj.thickness.toDouble() material.color = obj.color?.let { Color(it) } ?: DEFAULT_LINE_COLOR diff --git a/visionforge-solid/src/jsMain/kotlin/hep/dataforge/vision/solid/three/ThreeMaterials.kt b/visionforge-solid/src/jsMain/kotlin/hep/dataforge/vision/solid/three/ThreeMaterials.kt index a2da8611..7566e136 100644 --- a/visionforge-solid/src/jsMain/kotlin/hep/dataforge/vision/solid/three/ThreeMaterials.kt +++ b/visionforge-solid/src/jsMain/kotlin/hep/dataforge/vision/solid/three/ThreeMaterials.kt @@ -33,18 +33,27 @@ object ThreeMaterials { linewidth = 8.0 } - fun getLineMaterial(meta: Meta?): LineBasicMaterial { + private val lineMaterialCache = HashMap() + + private fun buildLineMaterial(meta: Meta): LineBasicMaterial = LineBasicMaterial().apply { + color = meta[SolidMaterial.COLOR_KEY]?.getColor() ?: DEFAULT_LINE_COLOR + opacity = meta[SolidMaterial.OPACITY_KEY].double ?: 1.0 + transparent = opacity < 1.0 + linewidth = meta["thickness"].double ?: 1.0 + } + + fun getLineMaterial(meta: Meta?, cache: Boolean): LineBasicMaterial { if (meta == null) return DEFAULT_LINE - return LineBasicMaterial().apply { - color = meta[SolidMaterial.COLOR_KEY]?.getColor() ?: DEFAULT_LINE_COLOR - opacity = meta[SolidMaterial.OPACITY_KEY].double ?: 1.0 - transparent = opacity < 1.0 - linewidth = meta["thickness"].double ?: 1.0 + return if (cache) { + lineMaterialCache.getOrPut(meta) { buildLineMaterial(meta) } + } else { + buildLineMaterial(meta) } } - fun getMaterial(vision3D: Vision): Material { - val meta = vision3D.getItem(SolidMaterial.MATERIAL_KEY).node ?: return DEFAULT + private val materialCache = HashMap() + + private fun buildMaterial(meta: Meta): Material { return if (meta[SolidMaterial.SPECULAR_COLOR_KEY] != null) { MeshPhongMaterial().apply { color = meta[SolidMaterial.COLOR_KEY]?.getColor() ?: DEFAULT_COLOR @@ -65,6 +74,15 @@ object ThreeMaterials { } } + fun getMaterial(vision3D: Vision, cache: Boolean): Material { + val meta = vision3D.getItem(SolidMaterial.MATERIAL_KEY).node ?: return DEFAULT + return if (cache) { + materialCache.getOrPut(meta) { buildMaterial(meta) } + } else { + buildMaterial(meta) + } + } + } /** diff --git a/visionforge-solid/src/jsMain/kotlin/hep/dataforge/vision/solid/three/ThreePlugin.kt b/visionforge-solid/src/jsMain/kotlin/hep/dataforge/vision/solid/three/ThreePlugin.kt index a3af79d4..d8f467fe 100644 --- a/visionforge-solid/src/jsMain/kotlin/hep/dataforge/vision/solid/three/ThreePlugin.kt +++ b/visionforge-solid/src/jsMain/kotlin/hep/dataforge/vision/solid/three/ThreePlugin.kt @@ -70,7 +70,7 @@ class ThreePlugin : AbstractPlugin() { obj.onChildrenChange(this) { name, child -> if (name.isEmpty()) { - logger.error { "Children change with empty namr on $group" } + logger.error { "Children change with empty name on $group" } return@onChildrenChange } diff --git a/visionforge-solid/src/jsMain/kotlin/hep/dataforge/vision/solid/three/ThreeProxyFactory.kt b/visionforge-solid/src/jsMain/kotlin/hep/dataforge/vision/solid/three/ThreeProxyFactory.kt index 9ae86b4d..2fd83dfa 100644 --- a/visionforge-solid/src/jsMain/kotlin/hep/dataforge/vision/solid/three/ThreeProxyFactory.kt +++ b/visionforge-solid/src/jsMain/kotlin/hep/dataforge/vision/solid/three/ThreeProxyFactory.kt @@ -4,12 +4,26 @@ import hep.dataforge.names.toName import hep.dataforge.vision.solid.Proxy import hep.dataforge.vision.solid.Proxy.Companion.PROXY_CHILD_PROPERTY_PREFIX import hep.dataforge.vision.solid.Solid +import info.laht.threekt.core.BufferGeometry import info.laht.threekt.core.Object3D +import info.laht.threekt.objects.Mesh +import kotlin.reflect.KClass class ThreeProxyFactory(val three: ThreePlugin) : ThreeFactory { private val cache = HashMap() - override val type = Proxy::class + override val type: KClass = Proxy::class + + private fun Object3D.replicate(): Object3D { + return when (this) { + is Mesh -> Mesh(geometry as BufferGeometry, material) + else -> clone(false) + }.also { obj: Object3D -> + children.forEach { child: Object3D -> + obj.add(child.replicate()) + } + } + } override fun invoke(obj: Proxy): Object3D { val template = obj.prototype @@ -17,16 +31,16 @@ class ThreeProxyFactory(val three: ThreePlugin) : ThreeFactory { three.buildObject3D(template) } - //val mesh = Mesh(templateMesh.geometry as BufferGeometry, templateMesh.material) - val object3D = cachedObject.clone() + val object3D: Object3D = cachedObject.replicate() + object3D.updatePosition(obj) - obj.onPropertyChange(this) { name-> + obj.onPropertyChange(this) { name -> if (name.first()?.body == PROXY_CHILD_PROPERTY_PREFIX) { val childName = name.first()?.index?.toName() ?: error("Wrong syntax for proxy child property: '$name'") val propertyName = name.cutFirst() val proxyChild = obj[childName] ?: error("Proxy child with name '$childName' not found") - val child = object3D.findChild(childName)?: error("Object child with name '$childName' not found") + val child = object3D.findChild(childName) ?: error("Object child with name '$childName' not found") child.updateProperty(proxyChild, propertyName) } else { object3D.updateProperty(obj, name) diff --git a/visionforge-solid/src/jsMain/kotlin/info/laht/threekt/objects/LineSegments.kt b/visionforge-solid/src/jsMain/kotlin/info/laht/threekt/objects/LineSegments.kt index 3925c7fb..b5a83ff0 100644 --- a/visionforge-solid/src/jsMain/kotlin/info/laht/threekt/objects/LineSegments.kt +++ b/visionforge-solid/src/jsMain/kotlin/info/laht/threekt/objects/LineSegments.kt @@ -34,4 +34,7 @@ import info.laht.threekt.materials.Material open external class LineSegments(geometry: BufferGeometry, material: Material) : Object3D { constructor(geometry: Geometry, material: Material) + + var geometry: BufferGeometry + var material: Material } \ No newline at end of file