Back to home page

EIC code displayed by LXR

 
 

    


Warning, /firebird/firebird-ng/src/app/utils/three-geometry-editor.spec.ts is written in an unsupported language. File is not indexed.

0001 import * as THREE from 'three';
0002 import { editThreeNodeContent, EditThreeNodeRule, clearGeometryEditingFlags } from './three-geometry-editor';
0003 import { Mesh, BoxGeometry, MeshBasicMaterial, Group, Object3D } from 'three';
0004 
0005 /**
0006  * Helper to create a simple test mesh
0007  */
0008 function createTestMesh(name: string): Mesh {
0009   const geometry = new BoxGeometry(1, 1, 1);
0010   const material = new MeshBasicMaterial({ color: 0xffffff });
0011   const mesh = new Mesh(geometry, material);
0012   mesh.name = name;
0013   return mesh;
0014 }
0015 
0016 /**
0017  * Helper to create a test node structure like BeamPipe
0018  */
0019 function createBeamPipeStructure(): Group {
0020   const root = new Group();
0021   root.name = 'BeamPipe_assembly';
0022 
0023   // Add v_upstream meshes
0024   const upstream1 = createTestMesh('v_upstream_wall_1');
0025   const upstream2 = createTestMesh('v_upstream_wall_2');
0026 
0027   // Add other meshes
0028   const downstream1 = createTestMesh('v_downstream_section_1');
0029   const downstream2 = createTestMesh('v_downstream_section_2');
0030   const center = createTestMesh('center_pipe');
0031 
0032   root.add(upstream1, upstream2, downstream1, downstream2, center);
0033 
0034   return root;
0035 }
0036 
0037 /**
0038  * Helper to create a hierarchical test node structure
0039  * BeamPipe_assembly
0040  *   └── v_upstream_coating (Group)
0041  *       ├── Left (Mesh)
0042  *       └── Right (Mesh)
0043  *   └── v_downstream_section (Mesh)
0044  *   └── center_pipe (Mesh)
0045  */
0046 function createHierarchicalBeamPipe(): Group {
0047   const root = new Group();
0048   root.name = 'BeamPipe_assembly';
0049 
0050   // v_upstream_coating group with child meshes
0051   const upstreamGroup = new Group();
0052   upstreamGroup.name = 'v_upstream_coating';
0053   upstreamGroup.add(createTestMesh('Left'));
0054   upstreamGroup.add(createTestMesh('Right'));
0055 
0056   // Other meshes at root level
0057   const downstream = createTestMesh('v_downstream_section');
0058   const center = createTestMesh('center_pipe');
0059 
0060   root.add(upstreamGroup, downstream, center);
0061 
0062   return root;
0063 }
0064 
0065 /**
0066  * Helper to create a structure where parent Group and child Mesh have same name pattern
0067  * This simulates real detector geometry where naming can be like:
0068  * BeamPipe_assembly
0069  *   └── v_upstream_coating (Group)
0070  *       └── v_upstream_coating (Mesh with same name as parent!)
0071  *   └── other_pipe (Mesh)
0072  */
0073 function createSameNameHierarchy(): Group {
0074   const root = new Group();
0075   root.name = 'BeamPipe_assembly';
0076 
0077   // Group and its child mesh have the same name - this is valid in Three.js
0078   const upstreamGroup = new Group();
0079   upstreamGroup.name = 'v_upstream_coating';
0080   const upstreamMesh = createTestMesh('v_upstream_coating'); // Same name as parent!
0081   upstreamGroup.add(upstreamMesh);
0082 
0083   const otherPipe = createTestMesh('other_pipe');
0084 
0085   root.add(upstreamGroup, otherPipe);
0086 
0087   return root;
0088 }
0089 
0090 /**
0091  * Count meshes matching a pattern in the tree
0092  */
0093 function countMeshesWithPattern(root: Object3D, pattern: RegExp): number {
0094   let count = 0;
0095   root.traverse((child) => {
0096     if (child instanceof Mesh && pattern.test(child.name)) {
0097       count++;
0098     }
0099   });
0100   return count;
0101 }
0102 
0103 /**
0104  * Count all objects with geometry (meshes and line segments)
0105  */
0106 function countObjectsWithGeometry(root: Object3D): number {
0107   let count = 0;
0108   root.traverse((child) => {
0109     if ((child as any).geometry) {
0110       count++;
0111     }
0112   });
0113   return count;
0114 }
0115 
0116 /**
0117  * Get all object names in tree
0118  */
0119 function getAllNames(root: Object3D): string[] {
0120   const names: string[] = [];
0121   root.traverse((child) => {
0122     if (child.name) {
0123       names.push(child.name);
0124     }
0125   });
0126   return names;
0127 }
0128 
0129 describe('three-geometry-editor', () => {
0130 
0131   describe('clearGeometryEditingFlags', () => {
0132     it('should clear geometryEditingSkipRules flag on all nodes', () => {
0133       const root = createBeamPipeStructure();
0134 
0135       // Set flags on some nodes
0136       root.traverse((child) => {
0137         child.userData['geometryEditingSkipRules'] = true;
0138       });
0139 
0140       // Verify flags are set
0141       let flaggedCount = 0;
0142       root.traverse((child) => {
0143         if (child.userData['geometryEditingSkipRules']) flaggedCount++;
0144       });
0145       expect(flaggedCount).toBeGreaterThan(0);
0146 
0147       // Clear flags
0148       clearGeometryEditingFlags(root);
0149 
0150       // Verify all flags are cleared
0151       root.traverse((child) => {
0152         expect(child.userData['geometryEditingSkipRules']).toBeUndefined();
0153       });
0154     });
0155   });
0156 
0157   describe('editThreeNodeContent with merge=false', () => {
0158 
0159     it('should process only meshes matching pattern when pattern is provided', () => {
0160       const root = createBeamPipeStructure();
0161 
0162       const rule: EditThreeNodeRule = {
0163         patterns: ['**/v_upstream*'],
0164         color: 0xff0000,
0165         merge: false,
0166         outline: false
0167       };
0168 
0169       editThreeNodeContent(root, rule);
0170 
0171       // Check that v_upstream meshes have the new color
0172       root.traverse((child) => {
0173         if (child instanceof Mesh && child.name.includes('v_upstream')) {
0174           expect((child.material as MeshBasicMaterial).color.getHex()).toBe(0xff0000);
0175         }
0176       });
0177     });
0178 
0179     it('should set geometryEditingSkipRules flag on processed meshes', () => {
0180       const root = createBeamPipeStructure();
0181       clearGeometryEditingFlags(root);
0182 
0183       const rule: EditThreeNodeRule = {
0184         patterns: ['**/v_upstream*'],
0185         color: 0xff0000,
0186         merge: false,
0187         outline: false
0188       };
0189 
0190       editThreeNodeContent(root, rule);
0191 
0192       // v_upstream meshes should have the flag set
0193       root.traverse((child) => {
0194         if (child instanceof Mesh && child.name.includes('v_upstream')) {
0195           expect(child.userData['geometryEditingSkipRules']).toBe(true);
0196         }
0197       });
0198 
0199       // Other meshes should NOT have the flag
0200       root.traverse((child) => {
0201         if (child instanceof Mesh && !child.name.includes('v_upstream')) {
0202           expect(child.userData['geometryEditingSkipRules']).toBeUndefined();
0203         }
0204       });
0205     });
0206 
0207     it('should skip meshes with geometryEditingSkipRules=true when processing without pattern', () => {
0208       const root = createBeamPipeStructure();
0209       clearGeometryEditingFlags(root);
0210 
0211       // First rule: process v_upstream meshes
0212       const rule1: EditThreeNodeRule = {
0213         patterns: ['**/v_upstream*'],
0214         color: 0xff0000,  // Red
0215         merge: false,
0216         outline: false
0217       };
0218       editThreeNodeContent(root, rule1);
0219 
0220       // Second rule: "the rest" (no pattern)
0221       const rule2: EditThreeNodeRule = {
0222         color: 0x00ff00,  // Green
0223         merge: false,
0224         outline: false
0225       };
0226       editThreeNodeContent(root, rule2);
0227 
0228       // v_upstream meshes should still be red (not overwritten by rule2)
0229       root.traverse((child) => {
0230         if (child instanceof Mesh && child.name.includes('v_upstream')) {
0231           expect((child.material as MeshBasicMaterial).color.getHex()).toBe(0xff0000);
0232         }
0233       });
0234 
0235       // Other meshes should be green
0236       root.traverse((child) => {
0237         if (child instanceof Mesh && !child.name.includes('v_upstream') && !child.name.includes('outline')) {
0238           expect((child.material as MeshBasicMaterial).color.getHex()).toBe(0x00ff00);
0239         }
0240       });
0241     });
0242 
0243     it('should not create outline of outline', () => {
0244       const root = createBeamPipeStructure();
0245       clearGeometryEditingFlags(root);
0246 
0247       const initialCount = countObjectsWithGeometry(root);
0248 
0249       // First rule: process v_upstream with outline
0250       const rule1: EditThreeNodeRule = {
0251         patterns: ['**/v_upstream*'],
0252         color: 0xff0000,
0253         merge: false,
0254         outline: true
0255       };
0256       editThreeNodeContent(root, rule1);
0257 
0258       // Should have created 2 outlines (for 2 v_upstream meshes)
0259       const afterRule1Count = countObjectsWithGeometry(root);
0260       expect(afterRule1Count).toBe(initialCount + 2);
0261 
0262       // Second rule: "the rest" with outline
0263       const rule2: EditThreeNodeRule = {
0264         color: 0x00ff00,
0265         merge: false,
0266         outline: true
0267       };
0268       editThreeNodeContent(root, rule2);
0269 
0270       // Should have created outlines only for the 3 remaining original meshes
0271       // NOT for the outline objects created by rule1
0272       const afterRule2Count = countObjectsWithGeometry(root);
0273       expect(afterRule2Count).toBe(initialCount + 2 + 3);  // 5 original + 2 outlines from rule1 + 3 outlines from rule2
0274 
0275       // Verify no "outline_outline" objects exist
0276       const names = getAllNames(root);
0277       const doubleOutlines = names.filter(n => n.includes('outline_outline') || n.includes('_outline_outlin'));
0278       expect(doubleOutlines).toEqual([]);
0279     });
0280   });
0281 
0282   describe('editThreeNodeContent with hierarchical structure', () => {
0283 
0284     it('should apply style to descendants when applyToDescendants is true (default)', () => {
0285       const root = createHierarchicalBeamPipe();
0286       clearGeometryEditingFlags(root);
0287 
0288       // Rule matching v_upstream_coating (a Group)
0289       const rule: EditThreeNodeRule = {
0290         patterns: ['**/v_upstream*'],
0291         color: 0xff0000,  // Red
0292         merge: false,
0293         outline: false
0294         // applyToDescendants defaults to true
0295       };
0296 
0297       editThreeNodeContent(root, rule);
0298 
0299       // Children of v_upstream_coating should be red
0300       root.traverse((child) => {
0301         if (child instanceof Mesh && (child.name === 'Left' || child.name === 'Right')) {
0302           expect((child.material as MeshBasicMaterial).color.getHex()).toBe(0xff0000);
0303         }
0304       });
0305     });
0306 
0307     it('should skip descendants in "the rest" rule when parent was processed', () => {
0308       const root = createHierarchicalBeamPipe();
0309       clearGeometryEditingFlags(root);
0310 
0311       // First rule: match v_upstream_coating
0312       const rule1: EditThreeNodeRule = {
0313         patterns: ['**/v_upstream_coating'],
0314         color: 0xff0000,  // Red
0315         merge: false,
0316         outline: false
0317       };
0318       editThreeNodeContent(root, rule1);
0319 
0320       // Second rule: "the rest" (no pattern)
0321       const rule2: EditThreeNodeRule = {
0322         color: 0x00ff00,  // Green
0323         merge: false,
0324         outline: false
0325       };
0326       editThreeNodeContent(root, rule2);
0327 
0328       // v_upstream_coating children (Left, Right) should still be red
0329       // They should not be overwritten by rule2 due to hierarchical skip
0330       root.traverse((child) => {
0331         if (child instanceof Mesh && (child.name === 'Left' || child.name === 'Right')) {
0332           expect((child.material as MeshBasicMaterial).color.getHex()).toBe(0xff0000);
0333         }
0334       });
0335 
0336       // Other meshes should be green
0337       root.traverse((child) => {
0338         if (child instanceof Mesh && child.name === 'center_pipe') {
0339           expect((child.material as MeshBasicMaterial).color.getHex()).toBe(0x00ff00);
0340         }
0341       });
0342     });
0343 
0344     it('should handle parent and child with same name pattern without duplicates', () => {
0345       const root = createSameNameHierarchy();
0346       clearGeometryEditingFlags(root);
0347 
0348       const initialCount = countObjectsWithGeometry(root);
0349       expect(initialCount).toBe(2); // v_upstream_coating mesh + other_pipe mesh
0350 
0351       // Rule matching both the Group and its child Mesh (same name pattern)
0352       const rule: EditThreeNodeRule = {
0353         patterns: ['**/v_upstream*'],
0354         color: 0xff0000,  // Red
0355         merge: false,
0356         outline: true
0357       };
0358 
0359       editThreeNodeContent(root, rule);
0360 
0361       // Should create only ONE outline (for the one mesh)
0362       const afterCount = countObjectsWithGeometry(root);
0363       expect(afterCount).toBe(initialCount + 1); // +1 outline for the mesh
0364 
0365       // The mesh should be styled
0366       let styledMeshCount = 0;
0367       root.traverse((child) => {
0368         if (child instanceof Mesh && child.name === 'v_upstream_coating') {
0369           expect((child.material as MeshBasicMaterial).color.getHex()).toBe(0xff0000);
0370           styledMeshCount++;
0371         }
0372       });
0373       expect(styledMeshCount).toBe(1); // Only one mesh with that name
0374     });
0375 
0376     it('should skip child in "the rest" when parent was matched even with same name', () => {
0377       const root = createSameNameHierarchy();
0378       clearGeometryEditingFlags(root);
0379 
0380       // First rule: match v_upstream pattern
0381       const rule1: EditThreeNodeRule = {
0382         patterns: ['**/v_upstream*'],
0383         color: 0xff0000,  // Red
0384         merge: false,
0385         outline: false
0386       };
0387       editThreeNodeContent(root, rule1);
0388 
0389       // Second rule: "the rest"
0390       const rule2: EditThreeNodeRule = {
0391         color: 0x00ff00,  // Green
0392         merge: false,
0393         outline: false
0394       };
0395       editThreeNodeContent(root, rule2);
0396 
0397       // v_upstream_coating mesh should still be red
0398       root.traverse((child) => {
0399         if (child instanceof Mesh && child.name === 'v_upstream_coating') {
0400           expect((child.material as MeshBasicMaterial).color.getHex()).toBe(0xff0000);
0401         }
0402       });
0403 
0404       // other_pipe should be green
0405       root.traverse((child) => {
0406         if (child instanceof Mesh && child.name === 'other_pipe') {
0407           expect((child.material as MeshBasicMaterial).color.getHex()).toBe(0x00ff00);
0408         }
0409       });
0410     });
0411 
0412     it('should not apply to descendants when applyToDescendants is false', () => {
0413       const root = createHierarchicalBeamPipe();
0414       clearGeometryEditingFlags(root);
0415 
0416       // Rule matching v_upstream_coating (a Group) with applyToDescendants=false
0417       const rule: EditThreeNodeRule = {
0418         patterns: ['**/v_upstream*'],
0419         color: 0xff0000,  // Red
0420         merge: false,
0421         outline: false,
0422         applyToDescendants: false
0423       };
0424 
0425       editThreeNodeContent(root, rule);
0426 
0427       // When applyToDescendants is false and the matched node is a Group (not Mesh),
0428       // no styling is applied because Groups don't have geometry.
0429       // Children of v_upstream_coating (Left, Right) should NOT be red
0430       root.traverse((child) => {
0431         if (child instanceof Mesh && (child.name === 'Left' || child.name === 'Right')) {
0432           expect((child.material as MeshBasicMaterial).color.getHex()).toBe(0xffffff);  // Original color
0433         }
0434       });
0435 
0436       // v_downstream_section also matches the pattern (**/v_upstream* doesn't match it - it's v_DOWNstream)
0437       // so it should remain original color
0438       root.traverse((child) => {
0439         if (child instanceof Mesh && child.name === 'v_downstream_section') {
0440           expect((child.material as MeshBasicMaterial).color.getHex()).toBe(0xffffff);  // Original color
0441         }
0442       });
0443     });
0444   });
0445 
0446   describe('editThreeNodeContent with merge=true', () => {
0447 
0448     it('should set geometryEditingSkipRules on merged mesh', () => {
0449       const root = createBeamPipeStructure();
0450       clearGeometryEditingFlags(root);
0451 
0452       const rule: EditThreeNodeRule = {
0453         patterns: ['**/v_upstream*'],
0454         merge: true,
0455         outline: false,
0456         newName: 'merged_upstream'
0457       };
0458 
0459       editThreeNodeContent(root, rule);
0460 
0461       // Find the merged mesh
0462       let mergedMesh: Mesh | null = null;
0463       root.traverse((child) => {
0464         if (child instanceof Mesh && child.name === 'merged_upstream') {
0465           mergedMesh = child;
0466         }
0467       });
0468 
0469       expect(mergedMesh).not.toBeNull();
0470       expect(mergedMesh!.userData['geometryEditingSkipRules']).toBe(true);
0471     });
0472 
0473     it('should not re-process merged meshes in subsequent rules without pattern', () => {
0474       const root = createBeamPipeStructure();
0475       clearGeometryEditingFlags(root);
0476 
0477       // First rule: merge v_upstream meshes
0478       const rule1: EditThreeNodeRule = {
0479         patterns: ['**/v_upstream*'],
0480         merge: true,
0481         outline: false,
0482         newName: 'merged_upstream',
0483         color: 0xff0000
0484       };
0485       editThreeNodeContent(root, rule1);
0486 
0487       // Second rule: "the rest"
0488       const rule2: EditThreeNodeRule = {
0489         color: 0x00ff00,
0490         merge: false,
0491         outline: false
0492       };
0493       editThreeNodeContent(root, rule2);
0494 
0495       // Merged mesh should still be red
0496       root.traverse((child) => {
0497         if (child instanceof Mesh && child.name === 'merged_upstream') {
0498           expect((child.material as MeshBasicMaterial).color.getHex()).toBe(0xff0000);
0499         }
0500       });
0501     });
0502   });
0503 });