protected TransformResult.Code DoTransform(T package, CancelableProgressing progress) { foreach (var instance in package.Source.Instances) { if (progress?.State.IsAboutCancelling ?? false) { return(TransformResult.Code.Canceled); } switch (PassInstance(instance, package)) { case TransformActionType.Copy: Copy(instance, package, false, progress); break; case TransformActionType.CopyWithInverse: Copy(instance, package, true, progress); break; case TransformActionType.Delegate: DelegateCopy(instance, package, progress); break; case TransformActionType.Drop: package.Log?.Add(new TransformLogEntry(new XbimInstanceHandle(instance), TransformAction.NotTransferred)); break; } progress?.NotifyOnProgressChange(1, Name); } return(TransformResult.Code.Finished); }
public async Task RemoveByName() { IfcStore.ModelProviderFactory.UseMemoryModelProvider(); using (var source = IfcStore.Open(@"Resources\Ifc4-Storey-With-4Walls.ifc")) { var stampBefore = SchemaValidator.OfModel(source); Assert.IsTrue(stampBefore.IsCompliantToSchema); Assert.AreEqual(4, source.Instances .OfType <IIfcPropertySet>() .Where(s => s.Name == "AllplanAttributes") .Count()); var request = new IfcPropertySetRemovalRequest(this.TestLoggerFactory) { ExludePropertySetByName = new string[] { "AllplanAttributes" }, IsNameMatchingCaseSensitive = false, // Common config IsLogEnabled = true, TargetStoreType = Xbim.IO.XbimStoreType.InMemoryModel, EditorCredentials = EditorCredentials }; var cp = new CancelableProgressing(true); cp.OnProgressChange += (sender, e) => TestLogger.LogDebug($"State {e.State}: Percentage = {e.Percentage}; State object = {e.StateObject}"); using (var result = await request.Run(source, cp)) { if (null != result.Cause) { TestLogger?.LogError("Exception: {0}, {1}, {2}", result.Cause, result.Cause.Message, result.Cause.StackTrace); } Assert.AreEqual(TransformResult.Code.Finished, result.ResultCode); Assert.AreEqual(0, result.Target.Instances .OfType <IIfcPropertySet>() .Where(s => s.Name == "AllplanAttributes") .Count()); var pset = result.Target.Instances .OfType <IIfcPropertySet>() .Where(s => s.Name == "AllplanAttributes Copy") .ToArray(); Assert.AreEqual(4, pset.Length); Assert.IsTrue(pset.All(p => p.Properties <IIfcProperty>().Count() == 3)); var stampAfter = SchemaValidator.OfModel(result.Target); Assert.IsTrue(stampAfter.IsCompliantToSchema); Assert.IsTrue(cp.State.State.HasFlag(ProgressTokenState.IsTerminated)); result.Target.SaveAsIfc(new FileStream("Ifc4-Storey-With-4Walls-AllplanAttributes-Copy-1.ifc", FileMode.Create)); } } }
private TransformResult CreateResultFromCode(TransformResult.Code code, CancelableProgressing cp) { if (cp?.State.IsAboutCancelling ?? false) { cp.State.MarkCanceled(); } if (cp?.State.HasErrors ?? false) { cp.State.MarkBroken(); } return(new TransformResult(code)); }
protected Func <TransformResult> FastForward(IModel source, CancelableProgressing progress) { if (!progress?.State.IsAlive ?? false) { throw new NotSupportedException($"Progress monitor already terminated."); } return(() => { progress?.State.MarkTerminated(); progress?.NotifyOnProgressEnd($"Running fast forward '{Name}' model copy ..."); return new TransformResult(TransformResult.Code.Finished, CreateTransformPackage(source, source)); }); }
public async Task KeepOrRemoveByName() { IfcStore.ModelProviderFactory.UseMemoryModelProvider(); using (var source = IfcStore.Open(@"Resources\Ifc4-SampleHouse.ifc")) { var stampBefore = SchemaValidator.OfModel(source); Assert.IsTrue(stampBefore.IsCompliantToSchema); var request = new IfcPropertySetRemovalRequest(this.TestLoggerFactory) { ExludePropertySetByName = new string[] { "Other" }, IncludePropertySetByName = new string[] { "Pset_SpaceCommon", "Other" }, IsNameMatchingCaseSensitive = false, FilterRuleStrategy = FilterRuleStrategyType.ExcludeBeforeInclude, // Common config IsLogEnabled = true, TargetStoreType = Xbim.IO.XbimStoreType.InMemoryModel, EditorCredentials = EditorCredentials }; var cp = new CancelableProgressing(true); cp.OnProgressChange += (sender, e) => TestLogger.LogDebug($"State {e.State}: Percentage = {e.Percentage}; State object = {e.StateObject}"); using (var result = await request.Run(source, cp)) { if (null != result.Cause) { TestLogger?.LogError("Exception: {0}, {1}, {2}", result.Cause, result.Cause.Message, result.Cause.StackTrace); } var psetsRemaining = result.Target.Instances .OfType <IIfcPropertySet>() .Select(s => s.Name.ToString()) .Distinct() .ToArray(); Assert.AreEqual(TransformResult.Code.Finished, result.ResultCode); Assert.AreEqual(1, psetsRemaining.Length); Assert.IsTrue(string.Equals("Pset_SpaceCommon", psetsRemaining[0], StringComparison.OrdinalIgnoreCase)); var stampAfter = SchemaValidator.OfModel(result.Target); Assert.IsTrue(stampAfter.IsCompliantToSchema); Assert.IsTrue(cp.State.State.HasFlag(ProgressTokenState.IsTerminated)); result.Target.SaveAsIfc(new FileStream("Ifc4-SampleHouse-Pset_SpaceCommon-Other.ifc", FileMode.Create)); } } }
public async Task OffsetShiftAndRotateTest2_New() { IfcStore.ModelProviderFactory.UseMemoryModelProvider(); using (var source = IfcStore.Open(@"Resources\Ifc4-SampleHouse.ifc")) { var stampBefore = SchemaValidator.OfModel(source); var testConfig = IfcAxisAlignment.LoadFromFile(@"Resources\IfcAlignmentTestAxis2.xml"); Assert.IsNotNull(testConfig); Assert.IsNotNull(testConfig.SourceReferenceAxis); Assert.IsNotNull(testConfig.TargetReferenceAxis); var request = new IfcPlacementTransformRequest(this.TestLoggerFactory) { AxisAlignment = testConfig, PlacementStrategy = IfcPlacementStrategy.NewRootPlacement, // Common config IsLogEnabled = true, TargetStoreType = Xbim.IO.XbimStoreType.InMemoryModel, EditorCredentials = EditorCredentials }; using (var cp = new CancelableProgressing(true)) { cp.OnProgressChange += (s, o) => TestLogger.LogDebug($"State {o.State}: Percentage = {o.Percentage}; State object = {o.StateObject}"); using (var result = await request.Run(source, cp)) { if (null != result.Cause) { TestLogger?.LogError("Exception: {0}, {1}, {2}", result.Cause, result.Cause.Message, result.Cause.StackTrace); } var rootPlacement = result.Target.Instances.OfType <IIfcLocalPlacement>().Where(i => i.PlacementRelTo == null).FirstOrDefault(); Assert.IsNotNull(rootPlacement.PlacesObject); Assert.IsFalse(rootPlacement.PlacesObject.Any(), "Root has no objects"); //Assert.AreEqual(TransformResult.Code.Finished, result.ResultCode); // TODO Specific tests var stampAfter = SchemaValidator.OfModel(result.Target); //Assert.AreEqual(stampBefore, stampAfter); result.Target.SaveAsIfc(new FileStream("Ifc4-SampleHouse-Transformed.ifc", FileMode.Create)); } } } }
/// <summary> /// Runs the model transformation. /// </summary> /// <param name="model">The IFC model</param> /// <returns>A scene</returns> public Task <IfcSceneExportSummary> Run(IModel model, CancelableProgressing monitor) { return(Task.Run(() => { try { return DoSceneModelTransfer(model, new IfcSceneExportSettings(Settings), monitor); } catch (Exception e) { monitor?.State.MarkBroken(); Logger.LogError("{0}: {1} [{2}]", e.GetType().Name, e.Message, e.StackTrace); return new IfcSceneExportSummary(model, Settings) { FailureReason = e }; } })); }
internal void Prepare(CancelableProgressing cancelableProgress) { PlacementTree = new XbimPlacementTree(Source, false); SourceRootPlacementsLabels = Source.Instances .OfType <IIfcLocalPlacement>() .Where(p => p.PlacementRelTo == null).Select(p => p.EntityLabel).ToArray(); Array.Sort(SourceRootPlacementsLabels); switch (AppliedPlacementStrategy) { case IfcPlacementStrategy.NewRootPlacement: if (Source.SchemaVersion == Xbim.Common.Step21.XbimSchemaVersion.Ifc2X3) { throw new NotSupportedException("IFC2x3 doesn't support new placements without object context. Consider using 'IfcPlacementStrategy.ChangeRootPlacements'"); } // Shift is absorbed by new placement SingletonShift = XbimVector3D.Zero; break; case IfcPlacementStrategy.ChangeRootPlacements: if (SourceRootPlacementsLabels.Length > 1) { // Mean, if more than one var entireTranslation = SourceRootPlacementsLabels.Select(l => PlacementTree[l].Translation).Aggregate((a, b) => a + b); SingletonShift = entireTranslation * (1.0 / SourceRootPlacementsLabels.Length); } else { // Precise offset of singleton root SingletonShift = PlacementTree[SourceRootPlacementsLabels[0]].Translation; } AppliedAxisAlignment.SourceReferenceAxis.Translate(SingletonShift, -UnitsPerMeterSource); break; default: throw new NotImplementedException($"Missing '{AppliedPlacementStrategy}'"); } }
private async Task TestIfcModelExport(string fileName, IfcSceneExportSettings settings) { IfcSceneExportSummary result; using (var store = IfcStore.Open(fileName)) { var exporter = new IfcSceneExporter(new XbimTesselationContext(TestLoggerFactory), TestLoggerFactory); exporter.Settings = settings; using (var monitor = new CancelableProgressing(true)) { result = await exporter.Run(store, monitor); } } Assert.IsNotNull(result, "Result exists"); Assert.IsTrue(result.ComponentCache.Count > 0, "There are exported components"); Assert.IsTrue(result.ComponentCache.Values .All(c => c.Representations.SelectMany(r => r.Bodies).All(b => b.Faces.Count > 0)), "All bodies have faces"); // Show default values too var formatter = new JsonFormatter(JsonFormatter.Settings.Default.WithFormatDefaultValues(true)); using (var jsonStream = File.CreateText($"{Path.GetFileNameWithoutExtension(fileName)}.json")) { var json = formatter.Format(result.Scene); jsonStream.WriteLine(json); jsonStream.Close(); TestLogger.LogInformation($"JSON example has been written."); } using (var binStream = File.Create($"{Path.GetFileNameWithoutExtension(fileName)}.scene")) { var binScene = result.Scene.ToByteArray(); binStream.Write(binScene, 0, binScene.Length); TestLogger.LogInformation($"Binary scene of {binScene.Length} bytes has been written."); } }
protected IfcStore CreateTargetModel(IModel sourcePattern, CancelableProgressing progress) { var storeType = TargetStoreType ?? DetectStorageType(sourcePattern); progress?.NotifyOnProgressChange($"Starting '{Name}' with '{storeType}' model implementation ..."); IfcStore target; if (null == EditorCredentials) { target = IfcStore.Create(sourcePattern.SchemaVersion, storeType); } else { target = IfcStore.Create(EditorCredentials, sourcePattern.SchemaVersion, storeType); } // Set model factors target.ModelFactors.Initialise( sourcePattern.ModelFactors.AngleToRadiansConversionFactor, sourcePattern.ModelFactors.LengthToMetresConversionFactor, sourcePattern.ModelFactors.Precision); return(target); }
/// <summary> /// Creates a new transformation task and starts it. /// </summary> /// <param name="aSource">The source model</param> /// <param name="progressReceiver">The progress receiver</param> /// <returns></returns> public Task <TransformResult> Run(IModel aSource, CancelableProgressing cancelableProgressing) { cancelableProgressing?.NotifyProgressEstimateChange(aSource.Instances.Count); return(Task.Run(PrepareInternally(aSource, cancelableProgressing))); }
/// <summary> /// Runs tessselation with Xbim scene context /// </summary> /// <param name="model">The model to be exported</param> /// <param name="summary">The scene export summary</param> /// <param name="monitor">The progress emitter instance</param> /// <returns>An enumerable of tesselated product representations</returns> public IEnumerable <IfcProductSceneRepresentation> Tesselate(IModel model, IfcSceneExportSummary summary, CancelableProgressing monitor) { ReadGeometryStore(model, monitor); short[] excludeTypeId = ExcludeExpressType.Select(t => model.Metadata.ExpressTypeId(t.ExpressName)).ToArray(); Array.Sort(excludeTypeId); // Start reading the geometry store built before using (var gReader = model.GeometryStore.BeginRead()) { int totalCount = gReader.ShapeGeometries.Count(); int currentCount = 0; // Product label vs. Component and candidate shape labels var packageCache = new SortedDictionary <int, TesselationPackage>(); // Compute contexts ComputeContextTransforms(gReader, summary, ContextsCreateFromSettings(gReader, summary)); monitor?.NotifyProgressEstimateUpdate(totalCount); foreach (var geometry in gReader.ShapeGeometries) { if (monitor?.State.IsAboutCancelling ?? false) { monitor.State.MarkCanceled(); break; } currentCount++; monitor?.State.UpdateDone(currentCount, "Running tesselation..."); monitor?.NotifyOnProgressChange(); if (geometry.ShapeData.Length <= 0) { // No geometry continue; } var shapes = gReader.ShapeInstancesOfGeometry(geometry.ShapeLabel) .Where(i => 0 > Array.BinarySearch(excludeTypeId, i.IfcTypeId) && i.RepresentationType == XbimGeometryRepresentationType.OpeningsAndAdditionsIncluded); if (!shapes.Any()) { // No shape instances continue; } using (var ms = new MemoryStream(((IXbimShapeGeometryData)geometry).ShapeData)) { using (var br = new BinaryReader(ms)) { XbimShapeTriangulation tr = br.ReadShapeTriangulation(); foreach (XbimShapeInstance shape in shapes) { if (monitor?.State.IsAboutCancelling ?? false) { monitor.State.MarkCanceled(); break; } var product = model.Instances[shape.IfcProductLabel] as IIfcProduct; // Try first to find the referenced component TesselationPackage pkg; if (!packageCache.TryGetValue(shape.IfcProductLabel, out pkg)) { // New component ToDo shape tuple built from component and todo ShapeGeometryLabel pkg = new TesselationPackage(gReader.ShapeInstancesOfEntity(product)); packageCache[shape.IfcProductLabel] = pkg; } var ctx = summary.ContextOf(shape.RepresentationContext); if (null == ctx) { Logger?.LogWarning($"Shape of representation #{shape.RepresentationContext} of product #{product.EntityLabel} out of context scope. Skipped."); continue; } // Check for representation var representation = GetOrCreateRepresentation(summary, shape, pkg); if (!pkg.IsShapeGeometryDone(geometry)) { AppendVertices(representation, summary, tr.Vertices); } // TODO Use "bias" definition to adjust biased local offsets var body = new FaceBody { Material = new RefId { Nid = shape.StyleLabel > 0 ? shape.StyleLabel : shape.IfcTypeId * -1 }, Transform = CreateTransform(summary, shape), PtSet = (uint)representation.Points.Count - 1, }; foreach (var face in tr.Faces) { if (face.Indices.Count % 3 != 0) { throw new NotSupportedException("Expecting triangular meshes only"); } // Translate Xbim face definition var bodyFace = new Face { IsPlanar = face.IsPlanar, Mesh = new Mesh { Type = FacetType.TriMesh, Orient = Orientation.Ccw } }; switch (face.NormalCount) { case 0: // No normals at all break; case 1: // Single normal bodyFace.IsPlanar = true; face.Normals[0].Normal.AppendTo(bodyFace.Mesh.Normal); break; default: // No planar face if (face.NormalCount != face.Indices.Count) { throw new NotSupportedException($"Incorrect count of normals per face mesh (expecting {face.Indices.Count}, have {face.NormalCount}"); } foreach (var n in face.Normals.Select(n => n.Normal)) { n.AppendTo(bodyFace.Mesh.Normal); } break; } bodyFace.Mesh.Vertex.AddRange(face.Indices.Select(i => (uint)i)); body.Faces.Add(bodyFace); } // Add body to known component representation.Bodies.Add(body); // Remove geometry label from todo list pkg.RemoveDone(shape); // If no shape instances left if (pkg.IsDone) { yield return(pkg.ToSceneRepresentation(shape.IfcProductLabel)); packageCache.Remove(shape.IfcProductLabel); } } } } } // Return most recent if (packageCache.Count > 0) { Logger?.LogWarning($"Detected {packageCache.Count} unfinished geometry entries. Missing shapes."); foreach (var e in packageCache) { // Announce missing components even if unfinished due to some reason Logger?.LogWarning($"IfcProduct #{e.Key} misses {e.Value.CountOpenInstances} shape(s)."); yield return(e.Value.ToSceneRepresentation(e.Key)); } } } monitor?.NotifyOnProgressChange("Done tesselation."); }
/// <summary> /// Reads the geometry from model if empty. /// </summary> /// <param name="model">The model</param> /// <param name="progressing">The progress emitter</param> /// <param name="forceUpdate">Whether to force an update of geometry store anyway</param> /// <returns>The given udpated state or a new state</returns> public IEnumerable <IIfcRepresentationContext> ReadGeometryStore(IModel model, CancelableProgressing progressing, bool forceUpdate = false) { // Use Xbim Model Context for geometry creation if (forceUpdate || (model.GeometryStore?.IsEmpty ?? false)) { progressing?.NotifyProgressEstimateUpdate(100); ReportProgressDelegate progressDelegate = (percent, userState) => { progressing?.State.UpdateDone(percent, userState.ToString()); progressing?.NotifyOnProgressChange(); }; var context = new Xbim3DModelContext(model, "model", null, Logger); context.CreateContext(progressDelegate, false); return(context.Contexts); } else { return(model.GeometryStore.BeginRead().ContextIds .Select(label => model.Instances[label]) .Cast <IIfcRepresentationContext>()); } }
protected IPersistEntity Copy(IPersistEntity instance, T package, bool withInverse, CancelableProgressing cp) { package.Log?.Add(new TransformLogEntry(new XbimInstanceHandle(instance), TransformAction.Transferred)); try { return(package.Target.InsertCopy(instance, package.Map, (p, o) => PropertyTransform(p, o, package, cp), withInverse, false)); } catch (Exception e) { Log?.LogError("Exception at #{2}{3}: '{0}' with '{1}'.", e.GetType().Name, e.Message, instance.EntityLabel, instance.ExpressType.Name); throw e; } }
protected override IPersistEntity DelegateCopy(IPersistEntity instance, IfcPlacementTransformPackage package, CancelableProgressing cp) { if (instance is IIfcLocalPlacement p) { Log?.LogInformation($"Changing placement '{p}'"); // Don't copy inverse references (products and children) var targetPlacement = Copy(instance, package, false, cp) as IIfcLocalPlacement; package.HandlePlacementCopy(p, targetPlacement); return(targetPlacement); } else { throw new NotSupportedException($"Illegal handling of '{instance}' requested."); } }
protected override TransformResult.Code DoPreprocessTransform(IfcPlacementTransformPackage package, CancelableProgressing progress) { Log?.LogInformation("({0}) Applying '{1}' strategy to model.", Name, PlacementStrategy); package.Prepare(progress); Log?.LogInformation("({0}) Got singleton offset shift of {1} [m].", Name, package.SingletonShift * (1 / package.UnitsPerMeterSource)); return(TransformResult.Code.Finished); }
protected override IPersistEntity DelegateCopy(IPersistEntity instance, IfcMetadataTransformPackage package, CancelableProgressing cp) { return(base.DelegateCopy(instance, package, cp)); }
protected virtual IPersistEntity DelegateCopy(IPersistEntity instance, T package, CancelableProgressing cp) { return(Copy(instance, package, true, cp)); }
protected virtual TransformResult.Code DoPostTransform(T package, CancelableProgressing progress) { return(TransformResult.Code.Finished); }
/// <summary> /// Delegate handling the instance's property transformation. By default all properties (relations and data) are /// forwarded as they are into the transformation queue. /// </summary> /// <param name="property">The meta property descriptor</param> /// <param name="hostObject">The hosting object</param> /// <param name="package">The task's work package</param> /// <param name="cp">Cancelable progress monitor</param> /// <returns></returns> protected virtual object PropertyTransform(ExpressMetaProperty property, object hostObject, T package, CancelableProgressing cp) { if (cp?.State.IsAboutCancelling ?? false) { return(null); } else { return(property?.PropertyInfo.GetValue(hostObject)); } }
// Runs the scene model export private IfcSceneExportSummary DoSceneModelTransfer(IModel model, IfcSceneExportSettings settings, CancelableProgressing progressing) { // Generate new summary var summary = new IfcSceneExportSummary(model, settings); // Transfer materials var materials = StylesToMaterial(model).ToDictionary(m => m.Id.Nid); summary.Scene.Materials.AddRange(materials.Values); Logger?.LogInformation("Starting model tesselation of {0}", model.Header.Name); // Retrieve enumeration of components having a geomety within given contexts var sceneRepresentations = TesselatorInstance.Tesselate(model, summary, progressing); Logger?.LogInformation("Starting model export of {0}", model.Header.Name); // Run transfer and log parents var parents = new HashSet <int>(); foreach (var sr in sceneRepresentations) { var p = model.Instances[sr.EntityLabel] as IIfcProduct; if (progressing?.State.IsAboutCancelling ?? false) { Logger?.LogInformation("Canceled model export of '{0}'", model.Header.FileName); progressing.State.MarkCanceled(); break; } Component c; if (!summary.ComponentCache.TryGetValue(p.EntityLabel, out c)) { int?optParent; c = CreateComponent(p, Enumerable.Empty <Classifier>(), out optParent); summary.ComponentCache.Add(p.EntityLabel, c); summary.Scene.Components.Add(c); if (optParent.HasValue) { parents.Add(optParent.Value); } } c.Representations.AddRange(sr.Representations); } // Check for remaining components (i.e. missing parents without geometry) parents.RemoveWhere(id => summary.ComponentCache.ContainsKey(id)); Queue <int> missingInstance = new Queue <int>(parents); while (missingInstance.Count > 0) { if (progressing?.State.IsAboutCancelling ?? false) { if (!progressing.State.IsCanceled) { Logger?.LogInformation("Canceled model export of '{0}'", model.Header.FileName); progressing.State.MarkCanceled(); } break; } if (model.Instances[missingInstance.Dequeue()] is IIfcProduct product) { Component c; if (!summary.ComponentCache.TryGetValue(product.EntityLabel, out c)) { int?optParent; c = CreateComponent(product, Enumerable.Empty <Classifier>(), out optParent); summary.ComponentCache.Add(product.EntityLabel, c); if (optParent.HasValue && !summary.ComponentCache.ContainsKey(optParent.Value)) { // Enqueue missing parents missingInstance.Enqueue(optParent.Value); } summary.Scene.Components.Add(c); } } } // Add default materials where required summary.Scene.Materials.AddRange( DefaultMaterials( model, summary.Scene.Components .SelectMany(c => c.Representations) .SelectMany(r => r.Bodies) .Select(b => b.Material) .Where(m => 0 > m.Nid) .Distinct() ) ); return(summary); }
/// <summary> /// Creates a new transformation task but doesn't start it. /// </summary> /// <param name="aSource">The source model</param> /// <param name="progressing">The porgressing token</param> /// <returns></returns> public Task <TransformResult> Prepare(IModel aSource, out CancelableProgressing progressing) { progressing = new CancelableProgressing(true); progressing.NotifyProgressEstimateChange(aSource.Instances.Count); return(new Task <TransformResult>(PrepareInternally(aSource, progressing))); }
protected Func <TransformResult> PrepareInternally(IModel aSource, CancelableProgressing cp) { if (!cp?.State.IsAlive ?? false) { throw new NotSupportedException($"Progress monitor already terminated."); } return(() => { List <TransformLogEntry> logEntries = new List <TransformLogEntry>(); IfcStore target = CreateTargetModel(aSource, cp); using (ITransaction txStore = target.BeginTransaction(Name)) { try { T package = CreateTransformPackage(aSource, target); if (!IsLogEnabled) { package.Log = null; } TransformResult.Code code; cp?.NotifyOnProgressChange($"Preparing '{Name}' ..."); if (TransformResult.Code.Finished != (code = DoPreprocessTransform(package, cp))) { return CreateResultFromCode(code, cp); } cp?.NotifyOnProgressChange(0, $"Preparation done."); cp?.NotifyOnProgressChange($"Running '{Name}' ..."); if (TransformResult.Code.Finished != (code = DoTransform(package, cp))) { return CreateResultFromCode(code, cp); } cp?.NotifyOnProgressChange(0, $"Transformation done."); cp?.NotifyOnProgressChange($"Post processing '{Name}' ..."); if (TransformResult.Code.Finished != (code = DoPostTransform(package, cp))) { return CreateResultFromCode(code, cp); } cp?.NotifyOnProgressChange(0, $"Post-processing done."); txStore.Commit(); return new TransformResult(code, package); } catch (Exception e) { cp?.State.MarkBroken(); txStore.RollBack(); return new TransformResult(TransformResult.Code.ExitWithError, e); } finally { cp?.State.MarkTerminated(); cp?.NotifyOnProgressEnd($"Transform '{Name}' has been finalized."); } } }); }