GDML optimization

This commit is contained in:
Alexander Nozik 2020-08-26 10:16:55 +03:00
parent 48705e6670
commit a5eba1789b
31 changed files with 671 additions and 404 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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