private void AcquireBlocks(List <LineD> RayGrid, List <IMyCubeGrid> ProjectedGrids, List <IMyCubeGrid> RealGrids, HashSet <IMySlimBlock> ProjectedBlocks, HashSet <IMySlimBlock> RealBlocks) { if (ProjectedGrids.Count > 0 && MyKernel.TermControls.ToolMode == true) { foreach (IMyCubeGrid Grid in ProjectedGrids) { if (Grid == null) { continue; } if (!SafeZonesHelper.IsActionAllowed(Grid.WorldAABB, MySafeZoneAction.Building, MyKernel.Block.EntityId)) { continue; } Grid.GetBlocksOnRay(RayGrid, ProjectedBlocks, IsBlockPlaceable); } //yield return true; } foreach (IMyCubeGrid Grid in RealGrids) { try { if (MyKernel.TermControls.ToolMode == true) { if (!SafeZonesHelper.IsActionAllowed(Grid.WorldAABB, MySafeZoneAction.Welding, MyKernel.Block.EntityId)) { continue; } Grid.GetBlocksOnRay(RayGrid, RealBlocks, IsBlockWeldable); } else { if (!SafeZonesHelper.IsActionAllowed(Grid.WorldAABB, MySafeZoneAction.Grinding, MyKernel.Block.EntityId)) { continue; } Grid.GetBlocksOnRay(RayGrid, RealBlocks, IsBlockGrindable); } } catch (Exception Scrap) { LogError("ProcessGrids", $"Error while processing real grid {Grid?.DisplayName}", Scrap); } } }
IEnumerator <bool> WeldAndPlaceBlocks(ICollection <IMySlimBlock> RealBlocks, ICollection <IMySlimBlock> ProjectedBlocks) { if (RealBlocks == null || ProjectedBlocks == null) { yield return(false); } if (RealBlocks.Count == 0 && ProjectedBlocks.Count == 0) { yield return(false); } if (MyKernel.TermControls.ToolMode == false) { yield return(false); } if (!SafeZonesHelper.IsActionAllowed(MyKernel.Block.GetPosition(), MySafeZoneAction.Welding | MySafeZoneAction.Building, MyKernel.Block.EntityId)) { yield return(false); } bool ShouldContinue = RealBlocks.Count > 0 || ProjectedBlocks.Count > 0; //MyKernel.SessionBase.Log.DebugLog.WriteToLog($"WeldAndPlaceBlocks", $"Entry Point: real blocks: {RealBlocks.Count}, projected blocks: {ProjectedBlocks.Count}, should continue: {ShouldContinue}"); if (!ShouldContinue) { yield return(false); } if (MyKernel.TermControls.DistanceMode && RealBlocks.Count > 0) { IMySlimBlock FarthestBlock = RealBlocks.OrderBy(x => Vector3D.DistanceSquared(x.CubeGrid.GridIntegerToWorld(x.Position), MyKernel.Block.GetPosition())).Last(); RealBlocks.Clear(); RealBlocks.Add(FarthestBlock); //MyKernel.SessionBase.Log.DebugLog.WriteToLog($"WeldAndPlaceBlocks", $"Selected block for distance mode"); } float SpeedRatio = NominalWorkingFrequency * MyKernel.TermControls.SpeedMultiplier * WeldGrindSpeedMultiplier * (VanillaToolConstants.WelderSpeed / RealBlocks.Count); float BoneFixSpeed = VanillaToolConstants.WelderBoneRepairSpeed * NominalWorkingFrequency; IMyProjector Projector = null; if (ProjectedBlocks.Count > 0) { Projector = ((ProjectedBlocks.First().CubeGrid as MyCubeGrid).Projector as IMyProjector); } //MyKernel.SessionBase.Log.DebugLog.WriteToLog($"WeldAndPlaceBlocks", $"Checked for projectors"); if (!MyAPIGateway.Session.CreativeMode) { Dictionary <string, int> RequiredComponentNames = new Dictionary <string, int>(); { Action worker = () => { foreach (IMySlimBlock Block in RealBlocks) { Block.GetMissingComponents(RequiredComponentNames); } }; bool completed = false; Action callback = () => completed = true; MyAPIGateway.Parallel.StartBackground(worker, callback); while (!completed) { yield return(true); } } //MyKernel.SessionBase.Log.DebugLog.WriteToLog($"WeldAndPlaceBlocks", $"Got missing components"); foreach (IMySlimBlock Block in ProjectedBlocks) { var FirstItem = ((MyCubeBlockDefinition)Block.BlockDefinition).Components[0].Definition.Id; if (RequiredComponentNames.ContainsKey(FirstItem.SubtypeName)) { RequiredComponentNames[FirstItem.SubtypeName] += 1; } else { RequiredComponentNames.Add(FirstItem.SubtypeName, 1); } } //MyKernel.SessionBase.Log.DebugLog.WriteToLog($"WeldAndPlaceBlocks", $"Got missing components for projected blocks"); Dictionary <MyItemType, float> RequiredComponents = new Dictionary <MyItemType, float>(); var list = RequiredComponentNames.ToList(); foreach (KeyValuePair <string, int> kvp in list) { //MyKernel.SessionBase.Log.DebugLog.WriteToLog($"WeldAndPlaceBlocks", $"Trying to generate itemtype..."); //MyKernel.SessionBase.Log.DebugLog.WriteToLog($"WeldAndPlaceBlocks", $"for {kvp.Key}"); MyItemType itemtype = new MyItemType("MyObjectBuilder_Component", kvp.Key); //MyKernel.SessionBase.Log.DebugLog.WriteToLog($"WeldAndPlaceBlocks", $"Generated item value"); //MyKernel.SessionBase.Log.DebugLog.WriteToLog($"WeldAndPlaceBlocks", $"(item: {itemtype.SubtypeId}, amount: {kvp.Value})"); RequiredComponents.Add(itemtype, kvp.Value); } //MyKernel.SessionBase.Log.DebugLog.WriteToLog($"WeldAndPlaceBlocks", $"Reassembled missing dictionary"); List <IMyTerminalBlock> InventoryOwners = new List <IMyTerminalBlock>(MyKernel.Inventory.InventoryOwners); InventoryOwners.Add(MyKernel.Tool); InventoryModule.CalculateMissingItems(RequiredComponents, MyKernel.Inventory.GetAggregateItemsFor(InventoryOwners)); //MyKernel.SessionBase.Log.DebugLog.WriteToLog($"WeldAndPlaceBlocks", $"Calculated aggregate missing components"); if (MyKernel.Tool.UseConveyorSystem) { Dictionary <MyItemType, float> _RqComponents = new Dictionary <MyItemType, float>(RequiredComponents); foreach (var RequiredComponent in RequiredComponents) { float pulledAmount = MyKernel.Inventory.TryTransferTo(ToolCargo, RequiredComponent.Key, RequiredComponent.Value, InventoryOwners); _RqComponents[RequiredComponent.Key] -= pulledAmount; } RequiredComponents = _RqComponents; //MyKernel.SessionBase.Log.DebugLog.WriteToLog($"WeldAndPlaceBlocks", $"Pulled missing components"); } MyKernel.ResponderModule.UpdateMissing(RequiredComponents); yield return(true); int weldedBlocks = 0; List <IMySlimBlock> UnweldedBlocks = new List <IMySlimBlock>(); foreach (IMySlimBlock Block in RealBlocks) { if (Block.CanContinueBuild(ToolCargo) || Block.HasDeformation) { if (MyKernel.Session.Settings.AllowAsyncWelding) { if (CanWeldInAsyncMode(Block)) { MyAPIGateway.Parallel.StartBackground(() => Weld(Block, SpeedRatio, BoneFixSpeed)); } else { Weld(Block, SpeedRatio, BoneFixSpeed); } } else { Weld(Block, SpeedRatio, BoneFixSpeed); } weldedBlocks++; if (!MyKernel.Session.Settings.AllowAsyncWelding && weldedBlocks != RealBlocks.Count - 1) { yield return(true); } } else { UnweldedBlocks.Add(Block); } } //MyKernel.SessionBase.Log.DebugLog.WriteToLog($"WeldAndPlaceBlocks", $"Welded real blocks"); if (weldedBlocks > 0) { yield return(true); } int placedBlocks = 0; List <IMySlimBlock> NonplacedBlocksByLimit = new List <IMySlimBlock>(); List <IMySlimBlock> NonplacedBlocksByResources = new List <IMySlimBlock>(); List <IMySlimBlock> PlaceableProjectedBlocks = new List <IMySlimBlock>(); foreach (IMySlimBlock Block in ProjectedBlocks) { MyCubeBlockDefinition blockDef = Block.BlockDefinition as MyCubeBlockDefinition; if (MyKernel.Tool.IsWithinWorldLimits(Projector, blockDef.BlockPairName, blockDef.PCU)) { var FirstItem = blockDef.Components[0].Definition.Id; if (ToolCargo.GetItemAmount(FirstItem) >= 1) { PlaceableProjectedBlocks.Add(Block); } else { NonplacedBlocksByResources.Add(Block); } } else { NonplacedBlocksByLimit.Add(Block); } } foreach (IMySlimBlock Block in PlaceableProjectedBlocks) { var FirstItem = ((MyCubeBlockDefinition)Block.BlockDefinition).Components[0].Definition.Id; Projector.Build(Block, MyKernel.Tool.OwnerId, MyKernel.Tool.EntityId, false); ToolCargo.RemoveItemsOfType(1, FirstItem); placedBlocks++; if (placedBlocks != ProjectedBlocks.Count - 1) { yield return(true); } } if (UnweldedBlocks.Count > 0) { StringBuilder UnweldedReport = new StringBuilder(); UnweldedReport.AppendLine($"Failed to weld {UnweldedBlocks.Count} blocks:"); List <IGrouping <MyObjectBuilderType, IMySlimBlock> > unweldedByType = UnweldedBlocks.GroupBy(x => x.BlockDefinition.Id.TypeId).ToList(); foreach (IGrouping <MyObjectBuilderType, IMySlimBlock> grouping in unweldedByType) { UnweldedReport.AppendLine($"{grouping.Key.ToString().Replace("MyObjectBuilder_", "")} ({grouping.Count()})"); } if (NonplacedBlocksByLimit.Count > 0 || NonplacedBlocksByResources.Count > 0) { UnweldedReport.AppendLine(); } MyKernel.ResponderModule.UpdateStatusReport(UnweldedReport, append: true); } if (NonplacedBlocksByLimit.Count > 0) { StringBuilder NonplacedReport = new StringBuilder(); NonplacedReport.AppendLine($"Failed to place {UnweldedBlocks.Count} blocks,"); NonplacedReport.AppendLine($"reached the PCU/block limit:"); List <IGrouping <MyObjectBuilderType, IMySlimBlock> > unplacedByType = UnweldedBlocks.GroupBy(x => x.BlockDefinition.Id.TypeId).ToList(); foreach (IGrouping <MyObjectBuilderType, IMySlimBlock> grouping in unplacedByType) { NonplacedReport.AppendLine($"{grouping.Key.ToString().Replace("MyObjectBuilder_", "")} ({grouping.Count()})"); } MyKernel.ResponderModule.UpdateStatusReport(NonplacedReport, append: true); } if (NonplacedBlocksByResources.Count > 0) { StringBuilder NonplacedReport = new StringBuilder(); NonplacedReport.AppendLine($"Failed to place {UnweldedBlocks.Count} blocks,"); NonplacedReport.AppendLine($"out of resources:"); List <IGrouping <MyObjectBuilderType, IMySlimBlock> > unplacedByType = UnweldedBlocks.GroupBy(x => x.BlockDefinition.Id.TypeId).ToList(); foreach (IGrouping <MyObjectBuilderType, IMySlimBlock> grouping in unplacedByType) { NonplacedReport.AppendLine($"{grouping.Key.ToString().Replace("MyObjectBuilder_", "")} ({grouping.Count()})"); } MyKernel.ResponderModule.UpdateStatusReport(NonplacedReport, append: true); } //MyKernel.SessionBase.Log.DebugLog.WriteToLog($"WeldAndPlaceBlocks", $"Placed projected blocks"); } else if (MyAPIGateway.Session.CreativeMode) { foreach (IMySlimBlock Block in RealBlocks) { if (MyKernel.Session.Settings.AllowAsyncWelding) { if (CanWeldInAsyncMode(Block)) { MyAPIGateway.Parallel.StartBackground(() => Weld(Block, SpeedRatio, BoneFixSpeed)); } else { Weld(Block, SpeedRatio, BoneFixSpeed); } } else { Weld(Block, SpeedRatio, BoneFixSpeed); } } foreach (IMySlimBlock Block in ProjectedBlocks) { Projector.Build(Block, MyKernel.Tool.OwnerId, MyKernel.Tool.EntityId, false); } } //MyKernel.SessionBase.Log.DebugLog.WriteToLog($"WeldAndPlaceBlocks", $"Exit Point"); yield return(false); }
protected override IEnumerator <bool> ProcessVoxels(HashSet <IMyVoxelBase> Voxels) { if (!MyKernel.Session.Settings.EnableDrilling) { yield return(false); } if (Voxels.Count == 0) { yield return(false); } if (MyKernel.TermControls.ToolMode == true) { yield return(false); } if (!SafeZonesHelper.IsActionAllowed(MyKernel.Block.GetPosition(), MySafeZoneAction.Drilling, MyKernel.Block.EntityId)) { yield return(false); } //WriteToLog("ProcessVoxels", $"Processing {Voxels.Count} voxels"); Stopwatch stopwatch = Stopwatch.StartNew(); LineD WorkingRay = new LineD(MyKernel.BeamDrawer.BeamStart, MyKernel.BeamDrawer.BeamEnd); foreach (MyVoxelBase Voxel in Voxels.OfType <MyVoxelBase>()) { if (!SafeZonesHelper.IsActionAllowed((Voxel as IMyEntity).WorldAABB, MySafeZoneAction.Drilling, MyKernel.Block.EntityId)) { continue; } if (Voxel.GetOrePriority() == -1) { continue; // Idk why, but the same early exit is found in MyShipDrill } Vector3D?VoxelHit = Voxel.GetClosestPointOnRay(WorkingRay, step: 0.99f); if (VoxelHit.HasValue) { MyKernel.ResponderModule.UpdateStatusReport($"\r\nFound a voxel", true); lock (BlockedVoxels) { if (!BlockedVoxels.ContainsKey(Voxel.EntityId)) { BlockedVoxels.Add(Voxel.EntityId, new List <Vector3I>()); } } Vector3D hitpos = VoxelHit.Value; //MyVoxelMaterialDefinition Material = Voxel.GetMaterialAt(ref hitpos); Vector3D CutoutSphereCenter = hitpos + (-WorkingRay.Direction * DrillingOffsetM); //stopwatch.Stop(); //WriteToLog("ProcessVoxels", $"Hit: {Math.Round(Vector3D.Distance(WorkingRay.From, hitpos), 2)}m, cutout: {Math.Round(Vector3D.Distance(WorkingRay.From, CutoutSphereCenter), 2)} m away, Material: {(Material != null ? Material.MaterialTypeName : "null")}"); //stopwatch.Start(); BoundingSphereD Cutout = new BoundingSphereD(CutoutSphereCenter, CutoutRadius); //List<Vector3I> VoxelPoints = Voxel.GetVoxelPointsInSphere(Cutout); Vector3I refCorner; Vector3I refMaxCorner; MyStorageData cutoutVoxels = Voxel.GetVoxelCacheInSphere(Cutout, out refCorner, out refMaxCorner); //WriteToLog("ProcessVoxels", $"Cutout cache Min is {Math.Round(Vector3D.Distance(hitpos, Voxel.PositionLeftBottomCorner + refCorner), 2)}m away"); //WriteToLog("ProcessVoxels", $"Cutout cache Max is {Math.Round(Vector3D.Distance(hitpos, Voxel.PositionLeftBottomCorner + refCorner + cutoutVoxels.Size3D), 2)}m away"); Dictionary <MyVoxelMaterialDefinition, float> Materials = new Dictionary <MyVoxelMaterialDefinition, float>(); int nullMaterials = 0; int totalPoints = 0; Stopwatch enumeratorWatch = new Stopwatch(); using (MyStorageData.MortonEnumerator VoxelLoop = new MyStorageData.MortonEnumerator(cutoutVoxels, MyStorageDataTypeEnum.Content | MyStorageDataTypeEnum.Material)) { MyKernel.ResponderModule.UpdateStatusReport($"Entered enumerator", true); enumeratorWatch.Start(); Dictionary <byte, float> RawMaterials = new Dictionary <byte, float>(); Vector3I VoxelInSphereCenter; Vector3 CoordsSystemOutValue; //MyVoxelCoordSystems.WorldPositionToLocalPosition(Cutout.Center, Voxel.PositionComp.WorldMatrix, Voxel.PositionComp.WorldMatrixInvScaled, Voxel.SizeInMetresHalf, out CoordsSystemOutValue); //VoxelInSphereCenter = new Vector3I(CoordsSystemOutValue / 1f) + Voxel.StorageMin; Vector3D sphereCenter = Cutout.Center; MyVoxelCoordSystems.WorldPositionToLocalPosition(Voxel.PositionLeftBottomCorner, ref sphereCenter, out CoordsSystemOutValue); VoxelInSphereCenter = new Vector3I(CoordsSystemOutValue / 1f) + Voxel.StorageMin; Vector3D ReconstructedPosition = Voxel.PositionLeftBottomCorner + VoxelInSphereCenter; //WriteToLog("ProcessVoxels", $"VoxelInSphereCenter is {Math.Round(Vector3D.Distance(hitpos, ReconstructedPosition), 2)}m away from hitpos"); double CutoutRadiusSquared = Cutout.Radius * Cutout.Radius; int processedPoints = 0; int skippedPoints = 0; int linearIndex = -1; for (bool move = true; move != false;) { enumeratorWatch.Start(); try { move = VoxelLoop.MoveNext(); } catch { move = false; MyKernel.ResponderModule.UpdateStatusReport("Keen enumerator died", true); } if (move) { linearIndex++; Vector3I voxelPoint; cutoutVoxels.ComputePosition(linearIndex, out voxelPoint); voxelPoint += refCorner; BoundingBoxD voxelBox = new BoundingBoxD(Voxel.PositionLeftBottomCorner + voxelPoint, Voxel.PositionLeftBottomCorner + voxelPoint + 1); var PointContainment = Cutout.Contains(voxelBox); bool Contacts = PointContainment.HasFlag(ContainmentType.Contains) || PointContainment.HasFlag(ContainmentType.Intersects); if (Contacts || Vector3D.DistanceSquared(ReconstructedPosition, Voxel.PositionLeftBottomCorner + voxelPoint) <= CutoutRadiusSquared) { bool pointBlocked = true; lock (BlockedVoxels) { pointBlocked = BlockedVoxels[Voxel.EntityId].Contains(voxelPoint); if (!pointBlocked) { BlockedVoxels[Voxel.EntityId].Add(voxelPoint); } } if (!pointBlocked) { byte ContentFillLevel = cutoutVoxels.Content(linearIndex); byte MaterialId = cutoutVoxels.Material(linearIndex); float MaterialFillRatio = ContentFillLevel / MyVoxelConstants.VOXEL_CONTENT_FULL_FLOAT; if (!RawMaterials.ContainsKey(MaterialId)) { RawMaterials.Add(MaterialId, MaterialFillRatio); } else { RawMaterials[MaterialId] += MaterialFillRatio; } processedPoints++; } else { skippedPoints++; } } else { skippedPoints++; } int voxelIterationLimit = MyKernel.Session.Settings.SpeedMultiplierAcceleratesDrilling ? VoxelIterationLimit * MyKernel.TermControls.SpeedMultiplier : VoxelIterationLimit; if (processedPoints > 0 && processedPoints % voxelIterationLimit == 0) { //MyKernel.ResponderModule.UpdateStatusReport($"Cutting out {Math.Round(Cutout.Radius * 2, 1)}m sphere:\r\n{linearIndex} voxels processed", append: true); enumeratorWatch.Stop(); yield return(true); } } totalPoints = linearIndex; } MyKernel.ResponderModule.UpdateStatusReport($"Total processed points: {totalPoints} ({processedPoints}/{skippedPoints})", true); enumeratorWatch.Stop(); MyKernel.ResponderModule.UpdateStatusReport($"EnumeratorWatch: {Math.Round(enumeratorWatch.Elapsed.TotalMilliseconds, 3)}ms", true); //WriteToLog("ProcessVoxels", $"Found {processedPoints} valid voxel points out of {totalPoints}", EemRdx.SessionModules.LoggingLevelEnum.DebugLog, true, 2000); foreach (KeyValuePair <byte, float> kvp in RawMaterials) { MyVoxelMaterialDefinition material = MyDefinitionManager.Static.GetVoxelMaterialDefinition(kvp.Key); if (material != null) { Materials.Add(material, kvp.Value * DrillingYield); } else { nullMaterials++; continue; } } } MyKernel.ResponderModule.UpdateStatusReport($"Quitted enumerator", true); //WriteToLog("ProcessVoxels", $"Found {VoxelPoints.Count} valid voxel points", EemRdx.SessionModules.LoggingLevelEnum.DebugLog, true, 2000); //WriteToLog("ProcessVoxels", $"Found {Materials.Count} valid materials{(nullMaterials > 0 ? $" and {nullMaterials} null materials" : "")}", EemRdx.SessionModules.LoggingLevelEnum.DebugLog, true, 2000); //foreach (var kvp in Materials) //{ // WriteToLog("ProcessVoxels", $"Material: {kvp.Key.MaterialTypeName} (mined ore: {(kvp.Key.MinedOre != null ? kvp.Key.MinedOre : "null")}), amount: {kvp.Value}"); //} MyKernel.ResponderModule.UpdateStatusReport($"Calculating materials", true); Dictionary <MyObjectBuilder_Ore, float> MaterialsToAdd = new Dictionary <MyObjectBuilder_Ore, float>(); int nullMinedOres = 0; foreach (KeyValuePair <MyVoxelMaterialDefinition, float> kvp in Materials) { MyVoxelMaterialDefinition material = kvp.Key; if (string.IsNullOrWhiteSpace(material.MinedOre)) { nullMinedOres++; continue; } try { if (MyKernel.TermControls.DumpStone && material.MinedOre == "Stone") { continue; } MyObjectBuilder_Ore oreBuilder = MyObjectBuilderSerializer.CreateNewObject <MyObjectBuilder_Ore>(material.MinedOre); oreBuilder.MaterialTypeName = new MyStringHash?(material.Id.SubtypeId); MyPhysicalItemDefinition oreDef = MyDefinitionManager.Static.GetPhysicalItemDefinition(oreBuilder); float MinedAmountInKg = (kvp.Value / 1000 / oreDef.Volume) * oreDef.Mass * kvp.Key.MinedOreRatio * 32; stopwatch.Stop(); //WriteToLog("ProcessVoxels", $"Found material {material.MaterialTypeName}, {Math.Round(kvp.Value)} points, mined amount would be {Math.Round(MinedAmountInKg)} kg"); stopwatch.Start(); MaterialsToAdd.Add(oreBuilder, MinedAmountInKg); } catch (Exception Scrap) { LogError("ProcessVoxels().dict_Materials.Iterate", $"Unknown error occurred on material {material.MinedOre}", Scrap); } } Stopwatch cutoutWatch = Stopwatch.StartNew(); //Voxel.Storage.WriteRange(cutoutVoxels, MyStorageDataTypeFlags.ContentAndMaterial, refCorner - 1, refMaxCorner + 1); Voxel.RootVoxel.PerformCutOutSphereFast(Cutout.Center, (float)Cutout.Radius, true); cutoutWatch.Stop(); //WriteToLog("ProcessVoxels", $"Sphere was cut in {cutoutWatch.ElapsedTicks} ticks, which is {Math.Round(cutoutWatch.Elapsed.TotalMilliseconds, 4)} ms"); foreach (var kvp in MaterialsToAdd) { MyDefinitionId OreId = kvp.Key.GetId(); MyItemType oreItemType = new MyItemType(OreId.TypeId, OreId.SubtypeId); float amountToAdd = kvp.Value; while (amountToAdd > 0.1f) { while (((float)ToolCargo.CurrentVolume / (float)ToolCargo.MaxVolume) > 0.9f) { yield return(true); } MyInventoryItem oreItem = new MyInventoryItem(oreItemType, 0, (VRage.MyFixedPoint)amountToAdd); float FittingAmount = (float)(ToolCargo as Sandbox.Game.MyInventory).ComputeAmountThatFits(OreId); if (FittingAmount > amountToAdd) { FittingAmount = amountToAdd; } ToolCargo.AddItems((VRage.MyFixedPoint)FittingAmount, kvp.Key); amountToAdd -= FittingAmount; //MyKernel.ResponderModule.UpdateStatusReport($"Adding {Math.Round(FittingAmount)}u of {OreId.SubtypeId}, {amountToAdd} remains", append: false); } } } else { MyKernel.ResponderModule.UpdateStatusReport($"No voxel found", true); } } stopwatch.Stop(); double ElapsedMs = stopwatch.Elapsed.TotalMilliseconds; //WriteToLog("ProcessVoxels", $"Voxels processed, elapsed time: {stopwatch.ElapsedTicks} ticks, which is {Math.Round(ElapsedMs, 3)} ms"); MyKernel.ResponderModule.UpdateStatusReport($"Cycle finished", true); yield return(false); }