From 2e3b63c0f464fac2c7213726efa453c272409f5e Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Mon, 6 Sep 2021 22:48:03 +0300 Subject: [PATCH] Composite Union is replaced by a Group. Colors for root --- .../kotlin/ru/mipt/npm/root/DObject.kt | 2 + .../kotlin/ru/mipt/npm/root/dRootToSolid.kt | 26 +++-- .../kotlin/ru/mipt/npm/root/rootColor.kt | 42 +++++++ .../kotlin/ru/mipt/npm/root/loadBMN.kt | 72 ------------ demo/playground/build.gradle.kts | 1 + .../src/jvmMain/kotlin/rootParser.kt | 104 ++++++++++++++++++ .../jvmMain/resources/root}/BM@N.root.json | 0 .../visionforge/solid/FXCompositeFactory.kt | 2 +- .../kscience/visionforge/gdml/gdmlLoader.kt | 4 +- .../kscience/visionforge/solid/Composite.kt | 28 ++++- .../kscience/visionforge/solid/SolidGroup.kt | 4 +- visionforge-threejs/build.gradle.kts | 2 +- .../visionforge/solid/three/ThreeCanvas.kt | 22 ++-- .../solid/three/ThreeCompositeFactory.kt | 5 +- 14 files changed, 207 insertions(+), 107 deletions(-) create mode 100644 cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/rootColor.kt delete mode 100644 cern-root-loader/src/jvmTest/kotlin/ru/mipt/npm/root/loadBMN.kt create mode 100644 demo/playground/src/jvmMain/kotlin/rootParser.kt rename {cern-root-loader/src/commonTest/resources => demo/playground/src/jvmMain/resources/root}/BM@N.root.json (100%) diff --git a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/DObject.kt b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/DObject.kt index 361e0ae2..e0933614 100644 --- a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/DObject.kt +++ b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/DObject.kt @@ -105,6 +105,8 @@ public class DGeoVolume(meta: Meta, refCache: DObjectCache) : DNamed(meta, refCa public val fShape: DGeoShape? by dObject(::DGeoShape) public val fMedium: DGeoMedium? by dObject(::DGeoMedium) + public val fFillColor: Int? by meta.int() + override val name: Name by lazy { Name.parse(fName.ifEmpty { "volume[${meta.hashCode().toUInt()}]" }) } } diff --git a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/dRootToSolid.kt b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/dRootToSolid.kt index 582389e2..0fa40340 100644 --- a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/dRootToSolid.kt +++ b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/dRootToSolid.kt @@ -1,12 +1,11 @@ package ru.mipt.npm.root -import space.kscience.dataforge.meta.double -import space.kscience.dataforge.meta.get -import space.kscience.dataforge.meta.int -import space.kscience.dataforge.meta.isEmpty +import space.kscience.dataforge.meta.* import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.plus +import space.kscience.dataforge.values.doubleArray import space.kscience.visionforge.solid.* +import space.kscience.visionforge.solid.SolidMaterial.Companion.MATERIAL_COLOR_KEY import kotlin.math.* private val volumesName = Name.EMPTY //"volumes".asName() @@ -50,9 +49,8 @@ private fun Solid.useMatrix(matrix: DGeoMatrix?) { val fTranslation by matrix.meta.doubleArray() translate(fTranslation) - if (matrix.meta["fRotationMatrix"] != null) { - val fRotationMatrix by matrix.meta.doubleArray() - rotate(fRotationMatrix) + matrix.meta["fRotation.fRotationMatrix"]?.value?.let { + rotate(it.doubleArray) } } "TGeoHMatrix" -> { @@ -77,15 +75,15 @@ private fun SolidGroup.addShape( val compositeType = when (node.typename) { "TGeoIntersection" -> CompositeType.INTERSECT "TGeoSubtraction" -> CompositeType.SUBTRACT - "TGeoUnion" -> CompositeType.UNION + "TGeoUnion" -> CompositeType.GROUP else -> error("Unknown bool node type ${node.typename}") } - composite(compositeType, name = name) { - addShape(node.fLeft!!, context, "left").also { + smartComposite(compositeType, name = name) { + addShape(node.fLeft!!, context, null).also { if (it == null) TODO() it.useMatrix(node.fLeftMat) } - addShape(node.fRight!!, context, "right").also { + addShape(node.fRight!!, context, null).also { if (it == null) TODO() it.useMatrix(node.fRightMat) } @@ -277,7 +275,11 @@ private fun SolidGroup.addRootVolume( } } - return ref(templateName, name) + ref(templateName, name) + }.apply { + volume.fFillColor?.let { + meta[MATERIAL_COLOR_KEY] = RootColors[it] + } } } diff --git a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/rootColor.kt b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/rootColor.kt new file mode 100644 index 00000000..9ea9c040 --- /dev/null +++ b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/rootColor.kt @@ -0,0 +1,42 @@ +package ru.mipt.npm.root + +public object RootColors { + private val colorMap = Array(924) { "white" } + + //colorMap[110] = "white" + + private val moreCol = listOf( + 11 to "c1b7ad4d4d4d6666668080809a9a9ab3b3b3cdcdcde6e6e6f3f3f3cdc8accdc8acc3c0a9bbb6a4b3a697b8a49cae9a8d9c8f83886657b1cfc885c3a48aa9a1839f8daebdc87b8f9a768a926983976e7b857d9ad280809caca6c0d4cf88dfbb88bd9f83c89a7dc08378cf5f61ac8f94a6787b946971d45a549300ff7b00ff6300ff4b00ff3300ff1b00ff0300ff0014ff002cff0044ff005cff0074ff008cff00a4ff00bcff00d4ff00ecff00fffd00ffe500ffcd00ffb500ff9d00ff8500ff6d00ff5500ff3d00ff2600ff0e0aff0022ff003aff0052ff006aff0082ff009aff00b1ff00c9ff00e1ff00f9ff00ffef00ffd700ffbf00ffa700ff8f00ff7700ff6000ff4800ff3000ff1800ff0000", + 201 to "5c5c5c7b7b7bb8b8b8d7d7d78a0f0fb81414ec4848f176760f8a0f14b81448ec4876f1760f0f8a1414b84848ec7676f18a8a0fb8b814ecec48f1f1768a0f8ab814b8ec48ecf176f10f8a8a14b8b848ecec76f1f1", + 390 to "ffffcdffff9acdcd9affff66cdcd669a9a66ffff33cdcd339a9a33666633ffff00cdcd009a9a00666600333300", + 406 to "cdffcd9aff9a9acd9a66ff6666cd66669a6633ff3333cd33339a3333663300ff0000cd00009a00006600003300", + 422 to "cdffff9affff9acdcd66ffff66cdcd669a9a33ffff33cdcd339a9a33666600ffff00cdcd009a9a006666003333", + 590 to "cdcdff9a9aff9a9acd6666ff6666cd66669a3333ff3333cd33339a3333660000ff0000cd00009a000066000033", + 606 to "ffcdffff9affcd9acdff66ffcd66cd9a669aff33ffcd33cd9a339a663366ff00ffcd00cd9a009a660066330033", + 622 to "ffcdcdff9a9acd9a9aff6666cd66669a6666ff3333cd33339a3333663333ff0000cd00009a0000660000330000", + 791 to "ffcd9acd9a669a66339a6600cd9a33ffcd66ff9a00ffcd33cd9a00ffcd00ff9a33cd66006633009a3300cd6633ff9a66ff6600ff6633cd3300ff33009aff3366cd00336600339a0066cd339aff6666ff0066ff3333cd0033ff00cdff9a9acd66669a33669a009acd33cdff669aff00cdff339acd00cdff009affcd66cd9a339a66009a6633cd9a66ffcd00ff6633ffcd00cd9a00ffcd33ff9a00cd66006633009a3333cd6666ff9a00ff9a33ff6600cd3300ff339acdff669acd33669a00339a3366cd669aff0066ff3366ff0033cd0033ff339aff0066cd00336600669a339acd66cdff009aff33cdff009acd00cdffcd9aff9a66cd66339a66009a9a33cdcd66ff9a00ffcd33ff9a00cdcd00ff9a33ff6600cd33006633009a6633cd9a66ff6600ff6633ff3300cd3300ffff339acd00666600339a0033cd3366ff669aff0066ff3366cd0033ff0033ff9acdcd669a9a33669a0066cd339aff66cdff009acd009aff33cdff009a", + 920 to "cdcdcd9a9a9a666666333333" + ) + + init { + colorMap[0] = "white" + colorMap[1] = "black" + colorMap[2] = "red" + colorMap[3] = "green" + colorMap[4] = "blue" + colorMap[5] = "yellow" + colorMap[6] = "magenta" + colorMap[7] = "cyan" + colorMap[8] = "rgb(89,212,84)" + colorMap[9] = "rgb(89,84,217)" + colorMap[10] = "white" + + moreCol.forEach { (n, s) -> + for (i in 0 until (s.length / 6)) { + colorMap[n + i] = "#" + s.substring(i * 6, (i + 1) * 6) + } + } + } + + public operator fun get(index: Int): String = colorMap[index] +} \ No newline at end of file diff --git a/cern-root-loader/src/jvmTest/kotlin/ru/mipt/npm/root/loadBMN.kt b/cern-root-loader/src/jvmTest/kotlin/ru/mipt/npm/root/loadBMN.kt deleted file mode 100644 index fdbcea6d..00000000 --- a/cern-root-loader/src/jvmTest/kotlin/ru/mipt/npm/root/loadBMN.kt +++ /dev/null @@ -1,72 +0,0 @@ -package ru.mipt.npm.root - -import kotlinx.serialization.json.JsonArray -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.jsonPrimitive -import ru.mipt.npm.root.serialization.TGeoManager -import space.kscience.dataforge.meta.Meta -import space.kscience.dataforge.meta.get -import space.kscience.dataforge.meta.isLeaf -import space.kscience.dataforge.values.string -import space.kscience.visionforge.solid.Solids -import java.nio.file.Paths -import java.time.Duration -import kotlin.io.path.writeText -import kotlin.system.measureTimeMillis - -private fun JsonElement.countTypes(): Sequence = sequence { - when (val json = this@countTypes) { - is JsonObject -> { - json["_typename"]?.let { yield(it.jsonPrimitive.content) } - json.values.forEach { yieldAll(it.countTypes()) } - } - is JsonArray -> { - json.forEach { - yieldAll(it.countTypes()) - } - } - else -> { - } - } -} - -private fun Meta.countTypes() :Sequence = sequence { - if(!isLeaf){ - get("_typename")?.value?.let { yield(it.string) } - items.forEach { yieldAll(it.value.countTypes()) } - } -} - -fun main() { - val string = TGeoManager::class.java.getResourceAsStream("/BM@N.root.json")!! - .readAllBytes().decodeToString() - val time = measureTimeMillis { - val geo = DGeoManager.parse(string) - - val sizes = geo.meta.countTypes().groupBy { it }.mapValues { it.value.size } - sizes.forEach { - println(it) - } - - val solid = geo.toSolid() - - Paths.get("BM@N.vf.json").writeText(Solids.encodeToString(solid)) - //println(Solids.encodeToString(solid)) - } - -// val json = Json.parseToJsonElement(string) -// val sizes = json.countTypes().groupBy { it }.mapValues { it.value.size } -// sizes.forEach { -// println(it) -// } -// -// val time = measureTimeMillis { -// val geo = TObject.decodeFromString(TGeoManager.serializer(), string) -// val solid = geo.toSolid() -// -// println(Solids.encodeToString(solid)) -// } -// - println(Duration.ofMillis(time)) -} \ No newline at end of file diff --git a/demo/playground/build.gradle.kts b/demo/playground/build.gradle.kts index 515cb165..f06c209e 100644 --- a/demo/playground/build.gradle.kts +++ b/demo/playground/build.gradle.kts @@ -55,6 +55,7 @@ kotlin { api(project(":visionforge-gdml")) api(project(":visionforge-plotly")) api(projects.visionforge.visionforgeMarkdown) + api(projects.visionforge.cernRootLoader) } } diff --git a/demo/playground/src/jvmMain/kotlin/rootParser.kt b/demo/playground/src/jvmMain/kotlin/rootParser.kt new file mode 100644 index 00000000..e8995009 --- /dev/null +++ b/demo/playground/src/jvmMain/kotlin/rootParser.kt @@ -0,0 +1,104 @@ +package space.kscience.visionforge.examples + +import ru.mipt.npm.root.DGeoManager +import ru.mipt.npm.root.serialization.TGeoManager +import ru.mipt.npm.root.toSolid +import space.kscience.dataforge.context.Context +import space.kscience.dataforge.meta.Meta +import space.kscience.dataforge.meta.get +import space.kscience.dataforge.meta.isLeaf +import space.kscience.dataforge.values.string +import space.kscience.visionforge.solid.Solids + + +private fun Meta.countTypes(): Sequence = sequence { + if (!isLeaf) { + get("_typename")?.value?.let { yield(it.string) } + items.forEach { yieldAll(it.value.countTypes()) } + } +} + +fun main() { + val context = Context { + plugin(Solids) + } + + val string = TGeoManager::class.java.getResourceAsStream("/root/BM@N.root.json")!! + .readAllBytes().decodeToString() + + val geo = DGeoManager.parse(string) + + + val sizes = geo.meta.countTypes().groupBy { it }.mapValues { it.value.size } + sizes.forEach { + println(it) + } + + + val solid = geo.toSolid() + + //Paths.get("BM@N.vf.json").writeText(Solids.encodeToString(solid)) + //println(Solids.encodeToString(solid)) + + context.makeVisionFile { + vision("canvas") { + solid +/* SolidGroup { + set( + "Coil", + solid.getPrototype("Coil".asName())!!.apply { + parent = null + } + ) + *//* group("Shade") { + y = 200 + color("red") + coneSurface( + bottomOuterRadius = 135, + bottomInnerRadius = 25, + height = 50, + topOuterRadius = 135, + topInnerRadius = 25, + angle = 1.5707964 + ) { + position = Point3D(79.6, 0, -122.1) + rotation = Point3D(-1.5707964, 0, 0) + } + coneSurface( + bottomOuterRadius = 135, + bottomInnerRadius = 25, + height = 50, + topOuterRadius = 135, + topInnerRadius = 25, + angle = 1.5707964 + ) { + position = Point3D(-79.6, 0, -122.1) + rotation = Point3D(1.5707964, 0, -3.1415927) + } + coneSurface( + bottomOuterRadius = 135, + bottomInnerRadius = 25, + height = 50, + topOuterRadius = 135, + topInnerRadius = 25, + angle = 1.5707964 + ) { + position = Point3D(79.6, 0, 122.1) + rotation = Point3D(1.5707964, 0, 0) + } + coneSurface( + bottomOuterRadius = 135, + bottomInnerRadius = 25, + height = 50, + topOuterRadius = 135, + topInnerRadius = 25, + angle = 1.5707964 + ) { + position = Point3D(-79.6, 0, 122.1) + rotation = Point3D(-1.5707964, 0, -3.1415927) + } + }*//* + }*/ + } + } +} \ No newline at end of file diff --git a/cern-root-loader/src/commonTest/resources/BM@N.root.json b/demo/playground/src/jvmMain/resources/root/BM@N.root.json similarity index 100% rename from cern-root-loader/src/commonTest/resources/BM@N.root.json rename to demo/playground/src/jvmMain/resources/root/BM@N.root.json diff --git a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/FXCompositeFactory.kt b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/FXCompositeFactory.kt index 1cdcf914..588f15cf 100644 --- a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/FXCompositeFactory.kt +++ b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/FXCompositeFactory.kt @@ -48,7 +48,7 @@ public class FXCompositeFactory(public val plugin: FX3DPlugin) : FX3DFactory firstCSG.union(secondCSG) + CompositeType.GROUP, CompositeType.UNION -> firstCSG.union(secondCSG) CompositeType.INTERSECT -> firstCSG.intersect(secondCSG) CompositeType.SUBTRACT -> firstCSG.difference(secondCSG) } diff --git a/visionforge-gdml/src/commonMain/kotlin/space/kscience/visionforge/gdml/gdmlLoader.kt b/visionforge-gdml/src/commonMain/kotlin/space/kscience/visionforge/gdml/gdmlLoader.kt index 8eca342c..8d5ebd49 100644 --- a/visionforge-gdml/src/commonMain/kotlin/space/kscience/visionforge/gdml/gdmlLoader.kt +++ b/visionforge-gdml/src/commonMain/kotlin/space/kscience/visionforge/gdml/gdmlLoader.kt @@ -215,12 +215,12 @@ private class GdmlLoader(val settings: GdmlLoaderOptions) { val first: GdmlSolid = solid.first.resolve(root) ?: error("") val second: GdmlSolid = solid.second.resolve(root) ?: error("") val type: CompositeType = when (solid) { - is GdmlUnion -> CompositeType.SUM // dumb sum for better performance + is GdmlUnion -> CompositeType.GROUP // dumb sum for better performance is GdmlSubtraction -> CompositeType.SUBTRACT is GdmlIntersection -> CompositeType.INTERSECT } - return composite(type, name) { + return smartComposite(type, name) { addSolid(root, first).withPosition( solid.resolveFirstPosition(root), solid.resolveFirstRotation(root), diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Composite.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Composite.kt index 00db285c..a68ff645 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Composite.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Composite.kt @@ -2,6 +2,7 @@ package space.kscience.visionforge.solid import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import space.kscience.dataforge.meta.isEmpty import space.kscience.dataforge.meta.update import space.kscience.visionforge.VisionBuilder import space.kscience.visionforge.VisionContainerBuilder @@ -9,7 +10,7 @@ import space.kscience.visionforge.VisionPropertyContainer import space.kscience.visionforge.set public enum class CompositeType { - SUM, // Dumb sum of meshes + GROUP, // Dumb sum of meshes UNION, //CSG union INTERSECT, SUBTRACT @@ -50,6 +51,31 @@ public inline fun VisionContainerBuilder.composite( return res } +/** + * A smart form of [Composite] that in case of [CompositeType.GROUP] creates a static group instead + */ +@VisionBuilder +public fun SolidGroup.smartComposite( + type: CompositeType, + name: String? = null, + builder: SolidGroup.() -> Unit, +): Solid = if (type == CompositeType.GROUP) { + val group = SolidGroup(builder) + if (name == null && group.meta.isEmpty()) { + //append directly to group if no properties are defined + group.children.forEach { (key, value) -> + value.parent = null + set(null, value) + } + this + } else { + set(name, group) + group + } +} else { + composite(type, name, builder) +} + @VisionBuilder public inline fun VisionContainerBuilder.union( name: String? = null, diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidGroup.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidGroup.kt index ec6dd5f1..604aac01 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidGroup.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidGroup.kt @@ -82,8 +82,8 @@ public fun SolidGroup(block: SolidGroup.() -> Unit): SolidGroup { @VisionBuilder public fun VisionContainerBuilder.group( name: Name? = null, - action: SolidGroup.() -> Unit = {}, -): SolidGroup = SolidGroup().apply(action).also { set(name, it) } + builder: SolidGroup.() -> Unit = {}, +): SolidGroup = SolidGroup().apply(builder).also { set(name, it) } /** * Define a group with given [name], attach it to this parent and return it. diff --git a/visionforge-threejs/build.gradle.kts b/visionforge-threejs/build.gradle.kts index a6407d13..37b8772f 100644 --- a/visionforge-threejs/build.gradle.kts +++ b/visionforge-threejs/build.gradle.kts @@ -5,5 +5,5 @@ plugins { dependencies { api(project(":visionforge-solid")) implementation(npm("three", "0.130.1")) - implementation(npm("three-csg-ts", "3.1.6")) + implementation(npm("three-csg-ts", "3.1.9")) } diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvas.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvas.kt index 3b5ae6ae..d715a20f 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvas.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvas.kt @@ -167,7 +167,7 @@ public class ThreeCanvas( } //Clipping planes - options.useProperty(Canvas3DOptions::clipping){clipping -> + options.useProperty(Canvas3DOptions::clipping) { clipping -> if (!clipping.meta.isEmpty()) { renderer.localClippingEnabled = true boundingBox?.let { boundingBox -> @@ -192,7 +192,7 @@ public class ThreeCanvas( } } - options.useProperty(Canvas3DOptions::size){ + options.useProperty(Canvas3DOptions::size) { canvas.style.apply { minWidth = "${options.size.minWith.toInt()}px" maxWidth = "${options.size.maxWith.toInt()}px" @@ -273,18 +273,14 @@ public class ThreeCanvas( return } if (this is Mesh) { - if (highlight) { - val edges = LineSegments( - EdgesGeometry(geometry), - material - ).apply { - name = edgesName - } - add(edges) - } else { - val highlightEdges = children.find { it.name == edgesName } - highlightEdges?.let { remove(it) } + val edges = getObjectByName(edgesName) ?: LineSegments( + EdgesGeometry(geometry), + material + ).also { + it.name = edgesName + add(it) } + edges.visible = highlight } else { children.filter { it.name != edgesName }.forEach { it.toggleHighlight(highlight, edgesName, material) diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCompositeFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCompositeFactory.kt index 5705459a..f57530b3 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCompositeFactory.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCompositeFactory.kt @@ -1,7 +1,6 @@ package space.kscience.visionforge.solid.three import CSG -import info.laht.threekt.core.Object3D import info.laht.threekt.objects.Mesh import space.kscience.dataforge.names.startsWith import space.kscience.visionforge.onPropertyChange @@ -38,11 +37,11 @@ public class ThreeCompositeFactory(public val three: ThreePlugin) : ThreeFactory override val type: KClass get() = Composite::class - override fun invoke(three: ThreePlugin, obj: Composite): Object3D { + override fun invoke(three: ThreePlugin, obj: Composite): Mesh { val first = three.buildObject3D(obj.first) as? Mesh ?: error("First part of composite is not a mesh") val second = three.buildObject3D(obj.second) as? Mesh ?: error("Second part of composite is not a mesh") return when (obj.compositeType) { - CompositeType.SUM, CompositeType.UNION -> CSG.union(first, second) + CompositeType.GROUP, CompositeType.UNION -> CSG.union(first, second) CompositeType.INTERSECT -> CSG.intersect(first, second) CompositeType.SUBTRACT -> CSG.subtract(first, second) }.apply {