forked from kscience/visionforge
GDML optimization
This commit is contained in:
parent
48705e6670
commit
a5eba1789b
@ -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) {
|
||||
|
@ -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<GDMLAppProps> { 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<GDMLAppProps> { 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<GDMLAppProps> { 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<GDMLAppProps> { 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<GDMLAppProps> { 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(
|
||||
|
@ -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)
|
||||
// }
|
||||
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -71,7 +71,7 @@ private object VariableBoxThreeFactory : ThreeFactory<Solid> {
|
||||
//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)
|
||||
|
||||
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -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
|
||||
|
2
gradlew
vendored
2
gradlew
vendored
@ -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
|
||||
|
21
gradlew.bat
vendored
21
gradlew.bat
vendored
@ -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
|
||||
|
@ -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<HTMLElement>.card(title: String, crossinline block: TagConsumer<HTMLElement>.() -> 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<DIV>.() -> Unit
|
||||
): ReactElement = div(joinStyles(classes, "container${size.suffix}"), block)
|
||||
block: StyledDOMBuilder<DIV>.() -> 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<DIV>.() -> Unit
|
||||
): ReactElement {
|
||||
block: StyledDOMBuilder<DIV>.() -> 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<DIV>.() -> Unit
|
||||
): ReactElement {
|
||||
return div(joinStyles(classes, "row"), block)
|
||||
block: StyledDOMBuilder<DIV>.() -> Unit
|
||||
): ReactElement = styledDiv{
|
||||
css{
|
||||
classes.add("row")
|
||||
}
|
||||
block()
|
||||
}
|
||||
|
||||
fun Element.renderObjectTree(
|
||||
|
@ -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 }
|
@ -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 <T> Vision.flowStatistics(statistics: (Name, Vision) -> T): Flow<T> = callbackFlow<T> {
|
||||
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<out Vision>) {
|
||||
val depth get() = name.length
|
||||
}
|
||||
|
||||
suspend fun Vision.flowStatistics(): Flow<DefaultVisionStatistics> = flowStatistics { name, vision ->
|
||||
DefaultVisionStatistics(name, vision::class)
|
||||
}
|
@ -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)
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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 <T, K> Flow<T>.countDistinctBy(selector: (T) -> K): Map<K, Int> {
|
||||
val counter = LinkedHashMap<K, AtomicInteger>()
|
||||
collect {
|
||||
val key = selector(it)
|
||||
counter.getOrPut(key) { AtomicInteger() }.incrementAndGet()
|
||||
}
|
||||
return counter.mapValues { it.value.toInt() }
|
||||
}
|
||||
|
||||
suspend fun <T> Flow<T>.countDistinct(): Map<T, Int> = countDistinctBy { it }
|
@ -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<Name, Meta>()
|
||||
|
||||
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<Name, Meta>()
|
||||
|
||||
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)
|
||||
}
|
@ -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<Int, Counter>()
|
||||
|
||||
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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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++
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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<Solid>.render(meta: Meta = Meta.EMPTY, action: SolidGroup.() -> Unit) =
|
||||
|
@ -35,7 +35,7 @@ abstract class MeshThreeFactory<in T : Solid>(
|
||||
|
||||
//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<in T : Solid>(
|
||||
}
|
||||
|
||||
//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<in T : Solid>(
|
||||
}
|
||||
|
||||
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"
|
||||
|
@ -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)
|
||||
|
@ -21,7 +21,7 @@ object ThreeLabelFactory : ThreeFactory<SolidLabel> {
|
||||
height = 1
|
||||
curveSegments = 1
|
||||
})
|
||||
return Mesh(textGeo, getMaterial(obj)).apply {
|
||||
return Mesh(textGeo, getMaterial(obj,true)).apply {
|
||||
updatePosition(obj)
|
||||
obj.onPropertyChange(this@ThreeLabelFactory) { _ ->
|
||||
//TODO
|
||||
|
@ -18,7 +18,7 @@ object ThreeLineFactory : ThreeFactory<PolyLine> {
|
||||
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
|
||||
|
@ -33,18 +33,27 @@ object ThreeMaterials {
|
||||
linewidth = 8.0
|
||||
}
|
||||
|
||||
fun getLineMaterial(meta: Meta?): LineBasicMaterial {
|
||||
private val lineMaterialCache = HashMap<Meta, LineBasicMaterial>()
|
||||
|
||||
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<Meta, Material>()
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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<Proxy> {
|
||||
private val cache = HashMap<Solid, Object3D>()
|
||||
|
||||
override val type = Proxy::class
|
||||
override val type: KClass<Proxy> = 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<Proxy> {
|
||||
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)
|
||||
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue
Block a user