From 7b0363db790761a883c1623ca0bad140d7cbf5f0 Mon Sep 17 00:00:00 2001 From: Dmitry Romanov Date: Mon, 3 Jun 2024 12:02:24 -0400 Subject: [PATCH] Rules for Three geometry editing --- .../calorimetry.prettifier.ts | 33 +++- firebird-ng/src/app/geometry.service.ts | 113 +++++++++--- .../main-display/main-display.component.ts | 17 +- firebird-ng/src/app/model/subdetector.ts | 23 +++ .../src/app/root-geometry.processor.ts | 8 +- .../src/app/three-geometry.processor.ts | 169 ++++++++++-------- .../src/app/utils/three-geometry-editor.ts | 87 +++++++++ .../src/app/utils/three-geometry-merge.ts | 8 +- 8 files changed, 354 insertions(+), 104 deletions(-) create mode 100644 firebird-ng/src/app/model/subdetector.ts create mode 100644 firebird-ng/src/app/utils/three-geometry-editor.ts diff --git a/firebird-ng/src/app/geometry-prettifiers/calorimetry.prettifier.ts b/firebird-ng/src/app/geometry-prettifiers/calorimetry.prettifier.ts index 3191349..56a4007 100644 --- a/firebird-ng/src/app/geometry-prettifiers/calorimetry.prettifier.ts +++ b/firebird-ng/src/app/geometry-prettifiers/calorimetry.prettifier.ts @@ -17,7 +17,14 @@ export class CalorimetryGeometryPrettifier { //console.log(crystals); // Merge crystals together - let mergeResult: MergeResult = mergeMeshList(crystals, node, "crystals"); + + + let mergeResult = mergeMeshList(crystals, node, "crystals"); + + if(!mergeResult) { + console.warn("didn't find crystals") + return; + } disposeOriginalMeshesAfterMerge(mergeResult) // outline crystals @@ -36,6 +43,10 @@ export class CalorimetryGeometryPrettifier { }); mergeResult = mergeMeshList([innerSupport, ring], node, "support", supportMaterial); + if(!mergeResult) { + console.warn("didn't find crystals") + return; + } disposeOriginalMeshesAfterMerge(mergeResult); @@ -54,6 +65,10 @@ export class CalorimetryGeometryPrettifier { let aeroGel = findObject3DNodes(node, "**/*aerogel*", "Mesh").nodes[0]; sensors.push(aeroGel); let mergeResult = mergeMeshList(sensors, node, "sensors"); + if(!mergeResult) { + console.warn("didn't find aerogel & cooling") + return; + } disposeOriginalMeshesAfterMerge(mergeResult) let filter = findObject3DNodes(node, "**/*filter*", "Mesh").nodes[0]; @@ -79,6 +94,10 @@ export class CalorimetryGeometryPrettifier { // Merge crystals together mergeResult = mergeMeshList(mirrors, node, "mirrors", mirrorsMaterial); + if(!mergeResult) { + console.warn("didn't find mirrors") + return; + } disposeOriginalMeshesAfterMerge(mergeResult) // Cleanup. Removing useless nodes that were left without geometries speeds up overall rendering @@ -113,6 +132,10 @@ export class CalorimetryGeometryPrettifier { let mergeResult = mergeMeshList(barsPrisms, node, "barsPrisms", barMat); + if(!mergeResult) { + console.warn("didn't find barPrisms") + return; + } disposeOriginalMeshesAfterMerge(mergeResult); createOutline(mergeResult.mergedMesh); @@ -120,12 +143,20 @@ export class CalorimetryGeometryPrettifier { // Rails let rails = findObject3DNodes(node, "**/*rail*", "Mesh").nodes; mergeResult = mergeMeshList(rails, node, "rails"); + if(!mergeResult) { + console.warn("didn't find rails") + return; + } disposeOriginalMeshesAfterMerge(mergeResult); createOutline(mergeResult.mergedMesh); // MCPs let mcps = findObject3DNodes(node, "**/*mcp*", "Mesh").nodes; mergeResult = mergeMeshList(mcps, node, "mcps"); + if(!mergeResult) { + console.warn("didn't find mcps") + return; + } disposeOriginalMeshesAfterMerge(mergeResult); // Cleanup. Removing useless nodes that were left without geometries speeds up overall rendering diff --git a/firebird-ng/src/app/geometry.service.ts b/firebird-ng/src/app/geometry.service.ts index 698781c..28a9fcc 100644 --- a/firebird-ng/src/app/geometry.service.ts +++ b/firebird-ng/src/app/geometry.service.ts @@ -8,12 +8,14 @@ import { editGeoNodes, findGeoManager, findGeoNodes, findSingleGeoNode, GeoAttBits, GeoNodeEditRule, printAllGeoBitsStatus, - EditActions, removeGeoNode, testGeoBit + EditActions, removeGeoNode, testGeoBit, getGeoNodesByLevel } from './utils/cern-root.utils'; import {build} from 'jsrootdi/geom'; import {BehaviorSubject} from "rxjs"; import {RootGeometryProcessor} from "./root-geometry.processor"; import {UserConfigService} from "./user-config.service"; +import {Subdetector} from "./model/subdetector"; +import {Object3D} from "three"; // constants.ts export const DEFAULT_GEOMETRY = 'epic-central-optimized'; @@ -23,14 +25,32 @@ export const DEFAULT_GEOMETRY = 'epic-central-optimized'; }) export class GeometryService { - rootGeometryProcessor = new RootGeometryProcessor(); + public rootGeometryProcessor = new RootGeometryProcessor(); + + /** Collection of subdetectors */ + public subdetectors: Subdetector[] = []; + + /** TGeoManager if available */ + public rootGeometry: any|null = null; + + /** Main/entry/root THREEJS geometry tree node with the whole geometry */ + public geometry: Object3D|null = null; + + public groupsByDetName: Map; + + constructor(private settings: UserConfigService) { + this.groupsByDetName = new Map ([ + ["hello", "world"] + ]) } - async loadGeometry() { + async loadGeometry(): Promise<{rootGeometry: any|null, threeGeometry: Object3D|null}> { + + this.subdetectors = []; //let url: string = 'assets/epic_pid_only.root'; //let url: string = 'https://eic.github.io/epic/artifacts/tgeo/epic_dirc_only.root'; // let url: string = 'https://eic.github.io/epic/artifacts/tgeo/epic_full.root'; @@ -40,36 +60,87 @@ export class GeometryService { this.settings.selectedGeometry.value: 'https://eic.github.io/epic/artifacts/tgeo/epic_full.root'; - console.time('[GeoSrv]: Total load geometry time'); - console.log(`[GeoSrv]: Loading file ${url}`) + console.time('[GeometryService]: Total load geometry time'); + console.log(`[GeometryService]: Loading file ${url}`) - console.time('[GeoSrv]: Open root file'); + console.time('[GeometryService]: Open root file'); const file = await openFile(url); // >oO debug console.log(file); - console.timeEnd('[GeoSrv]: Open root file'); + console.timeEnd('[GeometryService]: Open root file'); - console.time('[GeoSrv]: Reading geometry from file'); - const rootGeoManager = await findGeoManager(file) // await file.readObject(objectName); + console.time('[GeometryService]: Reading geometry from file'); + this.rootGeometry = await findGeoManager(file) // await file.readObject(objectName); // >oO - console.log(rootGeoManager); - console.timeEnd('[GeoSrv]: Reading geometry from file'); + console.log("Got TGeoManager. For inspection:") + console.log(this.rootGeometry); + console.timeEnd('[GeometryService]: Reading geometry from file'); - console.time('[GeoSrv]: Root geometry pre-processing'); - this.rootGeometryProcessor.process(rootGeoManager); - console.time('[GeoSrv]: Root geometry pre-processing'); + console.time('[GeometryService]: Root geometry pre-processing'); + this.rootGeometryProcessor.process(this.rootGeometry); + console.time('[GeometryService]: Root geometry pre-processing'); - analyzeGeoNodes(rootGeoManager, 1); + analyzeGeoNodes(this.rootGeometry, 1); // - console.time('[GeoSrv]: Build geometry'); - let rootObject3d = build(rootGeoManager, { numfaces: 500000000, numnodes: 50000000, instancing:-1, dflt_colors: false, vislevel: 100, doubleside:true, transparency:true}); - console.timeEnd('[GeoSrv]: Build geometry'); - // >oO console.log(geo); + console.time('[GeometryService]: Build geometry'); + this.geometry = build(this.rootGeometry, + { + numfaces: 500000000, + numnodes: 500000000, + instancing:-1, + dflt_colors: false, + vislevel: 200, + doubleside:true, + transparency:true + }); + console.timeEnd('[GeometryService]: Build geometry'); + + // Validate the geometry + if(!this.geometry) { + throw new Error("Geometry is null or undefined after TGeoPainter.build"); + } + + if(!this.geometry.children.length) { + throw new Error("Geometry is converted but empty. Anticipated 'world_volume' but got nothing"); + } + + if(!this.geometry.children[0].children.length) { + throw new Error("Geometry is converted but empty. Anticipated array of top level nodes (usually subdetectors) but got nothing"); + } + + // We now know it is not empty array + console.time('[GeometryService]: Map root geometry to threejs geometry'); + let topDetectorNodes = this.geometry.children[0].children; + for(const topNode of topDetectorNodes) { + + // Process name + const originalName = topNode.name; + const name = this.stripIdFromName(originalName); // Remove id in the end EcalN_21 => Ecal + + const rootGeoNodes = getGeoNodesByLevel(this.rootGeometry, 1).map(obj=>obj.geoNode); + const rootNode = rootGeoNodes.find(obj => obj.fName === originalName); + + let subdetector: Subdetector = { + sourceGeometry: rootNode, + sourceGeometryName: rootNode?.fName ?? "", + geometry: topNode, + name: this.stripIdFromName(originalName), + groupName: this.groupsByDetName.get(name) || "" + } + console.log(subdetector.name, subdetector); + this.subdetectors.push(subdetector); + } + console.timeEnd('[GeometryService]: Map root geometry to threejs geometry'); + + + console.timeEnd('[GeometryService]: Total load geometry time'); + return {rootGeometry: this.rootGeometry, threeGeometry: this.geometry}; + } - console.timeEnd('[GeoSrv]: Total load geometry time'); - return {rootGeoManager, rootObject3d}; + private stripIdFromName(name: string) { + return name.replace(/_\d+$/, ''); } } diff --git a/firebird-ng/src/app/main-display/main-display.component.ts b/firebird-ng/src/app/main-display/main-display.component.ts index 6657ae7..3a19b28 100644 --- a/firebird-ng/src/app/main-display/main-display.component.ts +++ b/firebird-ng/src/app/main-display/main-display.component.ts @@ -92,7 +92,11 @@ export class MainDisplayComponent implements OnInit { async loadGeometry(initiallyVisible=true, scale=10) { - let {rootGeoManager, rootObject3d} = await this.geomService.loadGeometry(); + + let {rootGeometry, threeGeometry} = await this.geomService.loadGeometry(); + if(!threeGeometry) return; + + let threeManager = this.eventDisplay.getThreeManager(); let uiManager = this.eventDisplay.getUIManager(); let openThreeManager: any = threeManager; @@ -103,12 +107,12 @@ export class MainDisplayComponent implements OnInit { // Set geometry scale if (scale) { - rootObject3d.scale.setScalar(scale); + threeGeometry.scale.setScalar(scale); } // Add root geometry to scene // console.log("CERN ROOT converted to Object3d: ", rootObject3d); - sceneGeometry.add(rootObject3d); + sceneGeometry.add(threeGeometry); @@ -129,7 +133,7 @@ export class MainDisplayComponent implements OnInit { child.material.dispose(); // Dispose the old material if it's a heavy object - let opacity = rootObject3d.userData.opacity ?? 1; + let opacity = threeGeometry.userData["opacity"] ?? 1; let transparent = opacity < 1; child.material = new MeshPhongMaterial({ @@ -162,7 +166,7 @@ export class MainDisplayComponent implements OnInit { }); // HERE WE DO POSTPROCESSING STEP - this.threeGeometryProcessor.process(rootObject3d); + this.threeGeometryProcessor.process(this.geomService.subdetectors); // Now we want to change the materials sceneGeometry.traverse( (child: any) => { @@ -458,6 +462,9 @@ export class MainDisplayComponent implements OnInit { jsonGeometry = jsonGeom; this.eventDisplay .getLoadingManager().itemLoaded("MyGeometry"); + }).catch(reason=> { + console.error("ERROR LOADING GEOMETRY"); + console.log(reason); }); diff --git a/firebird-ng/src/app/model/subdetector.ts b/firebird-ng/src/app/model/subdetector.ts new file mode 100644 index 0000000..4faef59 --- /dev/null +++ b/firebird-ng/src/app/model/subdetector.ts @@ -0,0 +1,23 @@ +import {Object3D} from "three"; + +/** + * Subdetector describes one of the main detectors like "TOF" or "Some Calorimeter" providing + * convenient interface to its geometry and some additional data not solely covered by three.js geometry + */ +export interface Subdetector { + + /** If available, the original geometry component, that was used for this subdetector, like ROOT geometry or GLTF */ + sourceGeometry: any | null; + + /** If available, a name of a node/part of source geometry that is responsible for this class */ + sourceGeometryName: string; + + /** ThreeJS geometry of the subdetector */ + geometry: Object3D; + + /** Name of the detector (may differ from geometry node name) */ + name: string; + + /** The name of the detectors group. Might be in */ + groupName: string; +} diff --git a/firebird-ng/src/app/root-geometry.processor.ts b/firebird-ng/src/app/root-geometry.processor.ts index b860880..9b78a93 100644 --- a/firebird-ng/src/app/root-geometry.processor.ts +++ b/firebird-ng/src/app/root-geometry.processor.ts @@ -75,12 +75,17 @@ export class RootGeometryProcessor { namePattern: "*/EcalBarrelScFi*", editRules: [ {pattern: "*/fiber_grid*", action: EditActions.Remove}, + {pattern: "*", action: EditActions.SetGeoBit, geoBit: GeoAttBits.kVisDaughters}, + {pattern: "*/*layer*", action: EditActions.SetGeoBit, geoBit: GeoAttBits.kVisThis}, + {pattern: "*/*layer*", action: EditActions.UnsetGeoBit, geoBit: GeoAttBits.kVisNone}, + {pattern: "*/*layer*", action: EditActions.UnsetGeoBit, geoBit: GeoAttBits.kVisDaughters}, ] }, { namePattern: "*/EcalBarrelImaging*", editRules: [ {pattern: "*/stav*", action: EditActions.RemoveChildren}, + {pattern: "*", action: EditActions.SetGeoBit, geoBit: GeoAttBits.kVisDaughters}, ] }, { @@ -139,7 +144,8 @@ export class RootGeometryProcessor { {pattern: "*/suppbar*", action: EditActions.Remove}, {pattern: "*/component*3", action: EditActions.RemoveSiblings}, ] - } + }, + ] public process(rootGeoManager:any):any { diff --git a/firebird-ng/src/app/three-geometry.processor.ts b/firebird-ng/src/app/three-geometry.processor.ts index 552eb1b..192285c 100644 --- a/firebird-ng/src/app/three-geometry.processor.ts +++ b/firebird-ng/src/app/three-geometry.processor.ts @@ -13,12 +13,38 @@ import {wildCardCheck} from "./utils/wildcard"; import {createOutline, disposeHierarchy, findObject3DNodes, pruneEmptyNodes} from "./utils/three.utils"; import {CalorimetryGeometryPrettifier} from "./geometry-prettifiers/calorimetry.prettifier"; import {mergeBranchGeometries} from "./utils/three-geometry-merge"; +import {editThreeNodeContent, EditThreeNodeRule} from "./utils/three-geometry-editor"; +import {Subdetector} from "./model/subdetector"; + + +export interface DetectorThreeRuleSet { + names?: string[]; + name?: string; + rules: EditThreeNodeRule[]; + + +} export class ThreeGeometryProcessor { + rules: DetectorThreeRuleSet[] = [ + { + names: ["FluxBarrel_env_25", "FluxEndcapP_26", "FluxEndcapN_28"], + rules: [] + }, + + { + name: "VertexBarrelSubAssembly_3", + rules: [] + } + + ] + calorimetry = new CalorimetryGeometryPrettifier(); + + glassMaterial = new THREE.LineBasicMaterial( { color: 0xf1f1f1, linewidth: 1, @@ -26,6 +52,8 @@ export class ThreeGeometryProcessor { linejoin: 'round' //ignored by WebGLRenderer } ); + + params = { alpha: 0.5, alphaHash: true, @@ -91,94 +119,89 @@ export class ThreeGeometryProcessor { vertexShader: this.vertexShader, fragmentShader: this.fragmentShader }); - } - public process(geometry: any) { + public process(detectors: Subdetector[]) { // Add top nodes to menu - let topDetectorNodes = geometry.children[0].children; + //let topDetectorNodes = geometry.children[0].children; // for(let i= topLevelObj3dNodes.length - 1; i >= 0; i--) { // console.log(`${i} : ${topLevelObj3dNodes[i].name}`); // } - console.log("DISPOSING"); - for(let i= topDetectorNodes.length - 1; i >= 0; i--){ - let detNode = topDetectorNodes[i]; - console.log(`${i} : ${topDetectorNodes[i].name}`); - detNode.name = detNode.userData["name"] = detNode.name; - // Add geometry - // uiManager.addGeometry(obj3dNode, obj3dNode.name); - - if(detNode.name == "EcalEndcapN_21") { - this.calorimetry.doEndcapEcalN(detNode); - } else if(detNode.name == "DRICH_16") { - this.calorimetry.doDRICH(detNode); - } else if(detNode.name.startsWith("DIRC")) { - this.calorimetry.doDIRC(detNode); - } else{ - - // try { - // detNode.removeFromParent(); - // } - // catch (e) { - // console.error(e); - // } - // - // try { - // // console.log("disposeHierarchy: ", detNode.name, detNode); - // disposeHierarchy(detNode); - // } catch (e) { - // console.error(e); - // } - - let result = mergeBranchGeometries(detNode, detNode.name + "_merged"); - createOutline(result.mergedMesh); - pruneEmptyNodes(detNode); - } - } - // Now we want to change the materials - // geometry.traverse( (child: any) => { + // console.log("DISPOSING"); + // for(let i= topDetectorNodes.length - 1; i >= 0; i--){ + // let detNode = topDetectorNodes[i]; + // console.log(`${i} : ${topDetectorNodes[i].name}`); + // detNode.name = detNode.userData["name"] = detNode.name; + // // Add geometry + // // uiManager.addGeometry(obj3dNode, obj3dNode.name); // - // if(child.type!=="Mesh") { - // return; - // } + // if(detNode.name == "EcalEndcapN_21") { + // this.calorimetry.doEndcapEcalN(detNode); + // } else if(detNode.name == "DRICH_16") { + // this.calorimetry.doDRICH(detNode); + // } else if(detNode.name.startsWith("DIRC")) { + // this.calorimetry.doDIRC(detNode); + // } else{ // - // child = child as THREE.Mesh; + // // try { + // // detNode.removeFromParent(); + // // } + // // catch (e) { + // // console.error(e); + // // } + // // + // // try { + // // // console.log("disposeHierarchy: ", detNode.name, detNode); + // // disposeHierarchy(detNode); + // // } catch (e) { + // // console.error(e); + // // } // + // let result = mergeBranchGeometries(detNode, detNode.name + "_merged"); + // createOutline(result.mergedMesh); + // (result.mergedMesh.material as any).onBeforeCompile = (shader: any) => { // - // if(!child?.material?.isMaterial) { - // return; - // } + // shader.fragmentShader = shader.fragmentShader.replace( // - // // Material - // let name:string = child.name; - // child.updateMatrixWorld(true); + // '#include ', // - // //if(name.startsWith("bar_") || name.startsWith("prism_")) { - // //child.material = this.alphaMaterial; - // const edges = new THREE.EdgesGeometry(child.geometry, 30); - // //const lineMaterial = new MeshLambertMaterial({ - // const lineMaterial = new THREE.LineBasicMaterial({ - // color: 0x555555, - // fog: false, - // // Copy clipping planes from parent, using type assertion for TypeScript - // clippingPlanes: child.material.clippingPlanes ? child.material.clippingPlanes : [], - // clipIntersection: false, - // clipShadows: true, - // transparent: false - // - // }); - // - // // lineMaterial.clipping = true; - // const edgesLine = new THREE.LineSegments(edges, lineMaterial); - // //const edgesLine = new Mesh(edges, lineMaterial); - // - // child.add(edgesLine); - // - // //} - // }); + // ` + // vec3 backfaceColor = vec3( 0.4, 0.4, 0.4 ); + // gl_FragColor = ( gl_FrontFacing ) ? vec4( outgoingLight, diffuseColor.a ) : vec4( backfaceColor, opacity ); + // ` + // ) + // }; + // pruneEmptyNodes(detNode); + // } + // } + } + + public processRuleSets(ruleSets: DetectorThreeRuleSet[], allDetectors: Subdetector[]) { + for(let ruleSet of ruleSets) { + this.processRuleSet(ruleSet, allDetectors); + } + } + + public processRuleSet(ruleSet: DetectorThreeRuleSet, allDetectors: Subdetector[]) { + let names: Set = new Set(ruleSet.names? ruleSet.names: []); + + // User provided names and name... this is probably a mistake, but we will just process all + // Such mistake happens when JSON rules are edited by users and they don't deserve an exception raise + if(ruleSet.name && !names.has(ruleSet.name)) { + names.add(ruleSet.name); + } + + for(const name of names) { + let detector = allDetectors.find(det => det.sourceGeometryName === name); + if (detector) { + for (let rule of ruleSet.rules) { + editThreeNodeContent(detector.geometry, rule); + } + } + } } } diff --git a/firebird-ng/src/app/utils/three-geometry-editor.ts b/firebird-ng/src/app/utils/three-geometry-editor.ts new file mode 100644 index 0000000..d161c49 --- /dev/null +++ b/firebird-ng/src/app/utils/three-geometry-editor.ts @@ -0,0 +1,87 @@ +import {Color, Material, Mesh, Object3D} from "three"; +import {createOutline, disposeOriginalMeshesAfterMerge, findObject3DNodes, pruneEmptyNodes} from "./three.utils"; +import {mergeBranchGeometries, mergeMeshList, MergeResult} from "./three-geometry-merge"; +import * as THREE from "three"; +import {ColorRepresentation} from "three/src/math/Color"; + +export enum EditThreeNodeActions { + + Merge, /** Merge children matching patterns (if patterns are provided) or all meshes of the node*/ + +} + +export interface EditThreeNodeRule { + + patterns?: string[] | string; + merge?: boolean; + newName?:string; + deleteOrigins?:boolean; + outline?:boolean; + outlineThresholdAngle?:number; /** [degrees] */ + outlineColor?:ColorRepresentation; + material?: Material; + color?: ColorRepresentation; +} + +function mergeWhatever(node: Object3D, rule: EditThreeNodeRule): MergeResult| undefined { + + let newName = !rule.newName? node.name + "_merged" : rule.newName; + + if(!rule.patterns) { + // If user provided patterns only children matching patterns (search goes over whole branch) will be merged, + // But if no patterns given, we will merge whole node + return mergeBranchGeometries(node, newName, rule.material); // Children auto removed + } + + // If we are here, we need to collect what to merge first + + let mergeSubjects = []; + // merge whole node + if(typeof rule.patterns === "string") { + rule.patterns = [rule.patterns]; + } + + for(const pattern of rule.patterns) { + mergeSubjects.push(...findObject3DNodes(node, pattern, "Mesh").nodes); + } + + let result = mergeMeshList(mergeSubjects, node, newName, rule.material); + if(result && rule.deleteOrigins) { + disposeOriginalMeshesAfterMerge(result); + } + return result; +} + +export function editThreeNodeContent(node: Object3D, rule: EditThreeNodeRule) { + + let {patterns, deleteOrigins = true, outline = true, outlineThresholdAngle = 40, outlineColor, material, color, merge=true, newName=""} = rule; + + let targetMesh: Mesh; + + if(merge) { + let result = mergeWhatever(node, rule); + if(!result) { + console.warn("didn't find children to merge. Patterns:"); + console.log(patterns) + return; + } + targetMesh = result.mergedMesh; + } + else { + targetMesh = node as Mesh; + } + + if(color) { + if(targetMesh.material) { + (targetMesh.material as any).color = color; + } + } + + if(outline) { + createOutline(targetMesh, {color: outlineColor, thresholdAngle: outlineThresholdAngle}); + } + + if(targetMesh) { + pruneEmptyNodes(targetMesh); + } +} diff --git a/firebird-ng/src/app/utils/three-geometry-merge.ts b/firebird-ng/src/app/utils/three-geometry-merge.ts index d238490..4534b86 100644 --- a/firebird-ng/src/app/utils/three-geometry-merge.ts +++ b/firebird-ng/src/app/utils/three-geometry-merge.ts @@ -105,7 +105,7 @@ export function mergeBranchGeometries(parentNode: THREE.Object3D, name: string, * @param material * @returns MergeResult The result of the merging process including the new parent node, merged geometry, material, and a list of original meshes. */ -export function mergeMeshList(meshes: THREE.Mesh[], parentNode: THREE.Object3D, name: string, material?: THREE.Material|undefined): MergeResult { +export function mergeMeshList(meshes: THREE.Mesh[], parentNode: THREE.Object3D, name: string, material?: THREE.Material|undefined): MergeResult|undefined { const geometries: THREE.BufferGeometry[] = []; // Collect geometries and materials from the provided meshes @@ -127,10 +127,12 @@ export function mergeMeshList(meshes: THREE.Mesh[], parentNode: THREE.Object3D, }); if (geometries.length === 0) { - throw new NoGeometriesFoundError(); + return undefined; + //throw new NoGeometriesFoundError(); } if (!material) { - throw new NoMaterialError(); + return undefined; + //throw new NoMaterialError(); } // Merge all collected geometries