From 38f5b4b8b58efd10e5d0d2efabfe8882b3e2c4f8 Mon Sep 17 00:00:00 2001 From: Claire Kuang Date: Mon, 14 Aug 2023 14:26:33 +0100 Subject: [PATCH 01/57] fix(civil): populates `curve` property of corridor featurelines (#2807) creates basecurve for corridor featurelines --- .../ConverterAutocadCivil.Civil.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Objects/Converters/ConverterAutocadCivil/ConverterAutocadCivilShared/ConverterAutocadCivil.Civil.cs b/Objects/Converters/ConverterAutocadCivil/ConverterAutocadCivilShared/ConverterAutocadCivil.Civil.cs index 2c0cea1815..0db6cc3ffd 100644 --- a/Objects/Converters/ConverterAutocadCivil/ConverterAutocadCivilShared/ConverterAutocadCivil.Civil.cs +++ b/Objects/Converters/ConverterAutocadCivil/ConverterAutocadCivilShared/ConverterAutocadCivil.Civil.cs @@ -680,14 +680,16 @@ public Featureline FeatureLineToSpeckle(CivilDB.FeatureLine featureline) private Featureline FeaturelineToSpeckle(CivilDB.CorridorFeatureLine featureline) { - // get all points and the display polyines + // get all points, the basecurve (no breaks) and the display polylines var points = new List(); var polylines = new List(); var polylinePoints = new Point3dCollection(); + var baseCurvePoints = new Point3dCollection(); for (int i = 0; i < featureline.FeatureLinePoints.Count; i++) { var point = featureline.FeatureLinePoints[i]; + baseCurvePoints.Add(point.XYZ); if (!point.IsBreak) { polylinePoints.Add(point.XYZ); } if (polylinePoints.Count > 0 && (i == featureline.FeatureLinePoints.Count - 1 || point.IsBreak )) { @@ -698,10 +700,12 @@ private Featureline FeaturelineToSpeckle(CivilDB.CorridorFeatureLine featureline } points.Add(PointToSpeckle(point.XYZ)); } + var baseCurve = PolylineToSpeckle(new Polyline3d(Poly3dType.SimplePoly, baseCurvePoints, false)); // create featureline var _featureline = new Featureline(); _featureline.points = points; + _featureline.curve = baseCurve; if (!string.IsNullOrEmpty(featureline.CodeName)) { _featureline.name = featureline.CodeName; } _featureline.displayValue = polylines; _featureline.units = ModelUnits; From 98aa934edd9091a879a6c6aa2250b1b651c3db36 Mon Sep 17 00:00:00 2001 From: connorivy <43247197+connorivy@users.noreply.github.com> Date: Tue, 15 Aug 2023 09:32:59 -0500 Subject: [PATCH 02/57] Fix(Revit) : Remove cool retractable roof feature (#2852) fix roof receiving Co-authored-by: Connor Ivy --- .../Partial Classes/ConvertRoof.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertRoof.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertRoof.cs index 433b8b0993..3e217070cc 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertRoof.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertRoof.cs @@ -65,6 +65,16 @@ public ApplicationObject RoofToNative(Roof speckleRoof) var start = ScaleToNative(speckleExtrusionRoof.start, speckleExtrusionRoof.units); var end = ScaleToNative(speckleExtrusionRoof.end, speckleExtrusionRoof.units); revitRoof = Doc.Create.NewExtrusionRoof(outline, plane, level, roofType, start, end); + + // sometimes Revit flips the roof so the start offset is the end and vice versa. + // In that case, delete the created roof, flip the referencePlane and recreate it. + var actualStart = GetParamValue(revitRoof, BuiltInParameter.EXTRUSION_START_PARAM); + if (actualStart - speckleExtrusionRoof.end < TOLERANCE) + { + Doc.Delete(revitRoof.Id); + plane.Flip(); + revitRoof = Doc.Create.NewExtrusionRoof(outline, plane, level, roofType, start, end); + } break; } case RevitFootprintRoof speckleFootprintRoof: From 98afc694873b77bcbbf17714c4747e597334793a Mon Sep 17 00:00:00 2001 From: Alan Rynne Date: Tue, 15 Aug 2023 17:52:05 +0200 Subject: [PATCH 03/57] fix(ci): Now using fixed win/server-2019 version as august release broke our pipeline (#2853) --- .circleci/scripts/config-template.yml | 6 ++---- .circleci/scripts/connector-jobs.yml | 2 -- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/.circleci/scripts/config-template.yml b/.circleci/scripts/config-template.yml index 7a29460839..1acc8a2280 100644 --- a/.circleci/scripts/config-template.yml +++ b/.circleci/scripts/config-template.yml @@ -504,15 +504,13 @@ jobs: # Each project will have individual jobs for each specific task it has to build-archicad-add-on: # build Archicad C++ add-on parameters: - e: - type: string - default: win/default archicadversion: type: string default: "" executor: - name: << parameters.e >> + name: win/server-2019 shell: bash.exe + version: 2023.04.1 # Version 2023.08.01 broke this step due to missing MSVC v142 C++ build tools. Fixed to the prior working version till a fix is issued. steps: - cached-checkout - attach_workspace: diff --git a/.circleci/scripts/connector-jobs.yml b/.circleci/scripts/connector-jobs.yml index 69b6628843..3c711e5a1e 100644 --- a/.circleci/scripts/connector-jobs.yml +++ b/.circleci/scripts/connector-jobs.yml @@ -124,13 +124,11 @@ csi: archicad: - build-archicad-add-on: - e: win/server-2019 archicadversion: "25" requires: - get-ci-tools name: build-archicad-add-on-25 - build-archicad-add-on: - e: win/server-2019 archicadversion: "26" requires: - get-ci-tools From 20870df21a93e6001d2331b6997b3c0b91760dd2 Mon Sep 17 00:00:00 2001 From: Claire Kuang Date: Wed, 16 Aug 2023 10:55:44 +0100 Subject: [PATCH 04/57] break(objects): adds new convert to native displayable bindings (#2846) * adds new convert to native displayable bindings * Update ConverterArchicad.cs * fix(core): Extra clarification on xml docs around new ISpeckleConverter methods * Update ISpeckleConverter.cs --------- Co-authored-by: Alan Rynne --- .../Converters/ConverterArchicad.cs | 334 +++++++++--------- Core/Core/Kits/ISpeckleConverter.cs | 31 ++ .../ConverterAutocadCivil.cs | 10 + .../ConverterBentley.cs | 19 +- .../ConverterCSIShared/ConverterCSI.cs | 143 ++++---- .../ConverterDynamoShared/ConverterDynamo.cs | 19 +- .../ConverterNavisworks.ToNative.cs | 14 +- .../ConverterRevitShared/ConverterRevit.cs | 12 +- .../ConverterRhinoGh.cs | 10 + .../ConverterTeklaStructures.cs | 31 +- 10 files changed, 377 insertions(+), 246 deletions(-) diff --git a/ConnectorArchicad/ConnectorArchicad/Converters/ConverterArchicad.cs b/ConnectorArchicad/ConnectorArchicad/Converters/ConverterArchicad.cs index 3ad57e4b05..e2805c7de6 100644 --- a/ConnectorArchicad/ConnectorArchicad/Converters/ConverterArchicad.cs +++ b/ConnectorArchicad/ConnectorArchicad/Converters/ConverterArchicad.cs @@ -1,165 +1,169 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Archicad; -using Objects.Geometry; -using Speckle.Core.Kits; -using Speckle.Core.Models; -using Speckle.Core.Models.GraphTraversal; -using static Speckle.Core.Models.ApplicationObject; - -namespace Objects.Converter.Archicad -{ - public partial class ConverterArchicad : ISpeckleConverter - { - public string Description => "Default Speckle Kit for Archicad"; - public string Name => nameof(ConverterArchicad); - public string Author => "Speckle"; - public string WebsiteOrEmail => "https://speckle.systems"; - - public IEnumerable GetServicedApplications() => new string[] { "Archicad" }; - - public ConversionOptions ConversionOptions { get; set; } - - /// - /// Keeps track of the conversion process - /// - public ProgressReport Report { get; private set; } = new ProgressReport(); - - /// - /// Decides what to do when an element being received already exists - /// - public ReceiveMode ReceiveMode { get; set; } - - // send - public Base ConvertToSpeckle(object @object) - { - return null; - } - - public List ConvertToSpeckle(List objects) => objects.Select(ConvertToSpeckle).ToList(); - - public bool CanConvertToSpeckle(object @object) - { - return false; - } - - // receive - public object ConvertToNative(Base @object) - { - return null; - } - - public List ConvertToNative(List objects) => objects.Select(ConvertToNative).ToList(); - - public bool CanConvertToNativeImplemented(Base @object) - { - return @object - switch - { - // Speckle BIM elements - Objects.BuiltElements.Beam _ => true, - Objects.BuiltElements.Column _ => true, - Objects.BuiltElements.Floor _ => true, - Objects.BuiltElements.Ceiling _ => true, - Objects.BuiltElements.Roof _ => true, - Objects.BuiltElements.Room _ => true, - Objects.BuiltElements.Wall _ => true, - - // Archicad elements - Objects.BuiltElements.Archicad.ArchicadDoor _ => true, - Objects.BuiltElements.Archicad.ArchicadWindow _ => true, - Objects.BuiltElements.Archicad.ArchicadSkylight _ => true, - Objects.BuiltElements.Archicad.DirectShape _ => true, - - // Revit elements - Objects.BuiltElements.Revit.FamilyInstance _ => true, - Objects.Other.Revit.RevitInstance _ => true, - - // Other - Objects.Other.BlockInstance _ => true, - - // Speckle geomtries - Objects.Geometry.Mesh _ => true, - Objects.Geometry.Brep _ => true, - - _ => false - }; - } - - public bool CanConvertToNativeNotImplemented(Base @object) - { - return @object - switch - { - // Project info - Objects.Organization.ModelInfo _ => true, - - _ => false - }; - } - - public bool CanConvertToNative(Base @object) - { - return CanConvertToNativeImplemented(@object) || CanConvertToNativeNotImplemented(@object); - } - - /// - /// To know which other objects are being converted, in order to sort relationships between them. - /// For example, elements that have children use this to determine whether they should send their children out or not. - /// - public List ContextObjects { get; set; } = new List(); - - /// - /// To keep track of previously received objects from a given stream in here. If possible, conversions routines - /// will edit an existing object, otherwise they will delete the old one and create the new one. - /// - public List PreviousContextObjects { get; set; } = new List(); - - public void SetContextDocument(object doc) - { - } - - public void SetContextObjects(List objects) => ContextObjects = objects; - - /// - /// Removes all inherited classes from speckle type string - /// - /// - /// - public static string SimplifySpeckleType(string type) - { - return type.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault(); - } - - public void SetContextObjects(List flattenObjects) - { - List objects; - - foreach (var tc in flattenObjects) - { - var applicationObject = new ApplicationObject(tc.current.id, SimplifySpeckleType(tc.current.speckle_type)) - { - applicationId = tc.current.applicationId, - Convertible = true - }; - - ContextObjects.Add(applicationObject); - } - } - - public void SetPreviousContextObjects(List objects) => PreviousContextObjects = objects; - - public void SetConverterSettings(object settings) - { - } - - public ConverterArchicad(ConversionOptions conversionOptions) - { - this.ConversionOptions = conversionOptions; - - var ver = System.Reflection.Assembly.GetAssembly(typeof(ConverterArchicad)).GetName().Version; - Report.Log($"Using converter: {Name} v{ver}"); - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using Archicad; +using Objects.Geometry; +using Speckle.Core.Kits; +using Speckle.Core.Models; +using Speckle.Core.Models.GraphTraversal; +using static Speckle.Core.Models.ApplicationObject; + +namespace Objects.Converter.Archicad +{ + public partial class ConverterArchicad : ISpeckleConverter + { + public string Description => "Default Speckle Kit for Archicad"; + public string Name => nameof(ConverterArchicad); + public string Author => "Speckle"; + public string WebsiteOrEmail => "https://speckle.systems"; + + public IEnumerable GetServicedApplications() => new string[] { "Archicad" }; + + public ConversionOptions ConversionOptions { get; set; } + + /// + /// Keeps track of the conversion process + /// + public ProgressReport Report { get; private set; } = new ProgressReport(); + + /// + /// Decides what to do when an element being received already exists + /// + public ReceiveMode ReceiveMode { get; set; } + + // send + public Base ConvertToSpeckle(object @object) + { + return null; + } + + public List ConvertToSpeckle(List objects) => objects.Select(ConvertToSpeckle).ToList(); + + public bool CanConvertToSpeckle(object @object) + { + return false; + } + + // receive + public object ConvertToNative(Base @object) + { + return null; + } + + public object ConvertToNativeDisplayable(Base @object) + { + throw new NotImplementedException(); + } + + public List ConvertToNative(List objects) => objects.Select(ConvertToNative).ToList(); + + public bool CanConvertToNativeImplemented(Base @object) + { + return @object switch + { + // Speckle BIM elements + Objects.BuiltElements.Beam _ => true, + Objects.BuiltElements.Column _ => true, + Objects.BuiltElements.Floor _ => true, + Objects.BuiltElements.Ceiling _ => true, + Objects.BuiltElements.Roof _ => true, + Objects.BuiltElements.Room _ => true, + Objects.BuiltElements.Wall _ => true, + + // Archicad elements + Objects.BuiltElements.Archicad.ArchicadDoor _ => true, + Objects.BuiltElements.Archicad.ArchicadWindow _ => true, + Objects.BuiltElements.Archicad.ArchicadSkylight _ => true, + Objects.BuiltElements.Archicad.DirectShape _ => true, + + // Revit elements + Objects.BuiltElements.Revit.FamilyInstance _ => true, + Objects.Other.Revit.RevitInstance _ => true, + + // Other + Objects.Other.BlockInstance _ => true, + + // Speckle geomtries + Objects.Geometry.Mesh _ => true, + Objects.Geometry.Brep _ => true, + + _ => false + }; + } + + public bool CanConvertToNativeNotImplemented(Base @object) + { + return @object switch + { + // Project info + Objects.Organization.ModelInfo _ => true, + + _ => false + }; + } + + public bool CanConvertToNative(Base @object) + { + return CanConvertToNativeImplemented(@object) || CanConvertToNativeNotImplemented(@object); + } + + public bool CanConvertToNativeDisplayable(Base @object) + { + return false; + } + + /// + /// To know which other objects are being converted, in order to sort relationships between them. + /// For example, elements that have children use this to determine whether they should send their children out or not. + /// + public List ContextObjects { get; set; } = new List(); + + /// + /// To keep track of previously received objects from a given stream in here. If possible, conversions routines + /// will edit an existing object, otherwise they will delete the old one and create the new one. + /// + public List PreviousContextObjects { get; set; } = new List(); + + public void SetContextDocument(object doc) { } + + public void SetContextObjects(List objects) => ContextObjects = objects; + + /// + /// Removes all inherited classes from speckle type string + /// + /// + /// + public static string SimplifySpeckleType(string type) + { + return type.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault(); + } + + public void SetContextObjects(List flattenObjects) + { + List objects; + + foreach (var tc in flattenObjects) + { + var applicationObject = new ApplicationObject(tc.current.id, SimplifySpeckleType(tc.current.speckle_type)) + { + applicationId = tc.current.applicationId, + Convertible = true + }; + + ContextObjects.Add(applicationObject); + } + } + + public void SetPreviousContextObjects(List objects) => PreviousContextObjects = objects; + + public void SetConverterSettings(object settings) { } + + public ConverterArchicad(ConversionOptions conversionOptions) + { + this.ConversionOptions = conversionOptions; + + var ver = System.Reflection.Assembly.GetAssembly(typeof(ConverterArchicad)).GetName().Version; + Report.Log($"Using converter: {Name} v{ver}"); + } + } +} diff --git a/Core/Core/Kits/ISpeckleConverter.cs b/Core/Core/Kits/ISpeckleConverter.cs index ad41d1ae87..eb46cb4578 100644 --- a/Core/Core/Kits/ISpeckleConverter.cs +++ b/Core/Core/Kits/ISpeckleConverter.cs @@ -55,6 +55,23 @@ public interface ISpeckleConverter /// public List ConvertToNative(List objects); + /// + /// Converts a given speckle objects as a generic native object. + /// This should assume has been called and returned True, + /// or call it within this method's implementation to ensure non-displayable objects are gracefully handled. + /// + /// + /// This method should not try to convert an object to it's native representation (i.e Speckle Wall -> Wall), + /// but rather use the 'displayValue' of that wall to create a geometrically correct representation of that object + /// in the native application. + /// An object may be able to be converted both with and . + /// In this case, deciding which to use is dependent on each connector developer. + /// Preferably, should be used as a fallback to the logic. + /// + /// Speckle object to convert + /// The native object that resulted after converting the input + public object ConvertToNativeDisplayable(Base @object); + /// /// Checks if it can convert a Speckle object to a native one /// @@ -62,6 +79,20 @@ public interface ISpeckleConverter /// public bool CanConvertToNative(Base @object); + /// + /// Checks to verify if a given object is: 1) displayable and 2) can be supported for conversion to the native application. + /// An object is considered "displayable" if it has a 'displayValue' property (defined in its class or dynamically attached to it, detached or not). + /// + /// + /// An object may return "True" for both and + /// In this case, deciding which to use is dependent on each connector developer. + /// Preferably, should be used as a fallback to the logic. + /// Objects found in the 'displayValue' property are assumed to be universally convertible by all converters and the viewer, but are not guaranteed to be so. + /// + /// Speckle object to convert + /// True if the object is "displayable" and the converter supports native conversion of the given speckle object in particular. + public bool CanConvertToNativeDisplayable(Base @object); + /// /// Returns a list of applications serviced by this converter /// diff --git a/Objects/Converters/ConverterAutocadCivil/ConverterAutocadCivilShared/ConverterAutocadCivil.cs b/Objects/Converters/ConverterAutocadCivil/ConverterAutocadCivilShared/ConverterAutocadCivil.cs index 5f741d7b10..bf01ef766b 100644 --- a/Objects/Converters/ConverterAutocadCivil/ConverterAutocadCivilShared/ConverterAutocadCivil.cs +++ b/Objects/Converters/ConverterAutocadCivil/ConverterAutocadCivilShared/ConverterAutocadCivil.cs @@ -428,6 +428,11 @@ public object ConvertToNative(Base @object) return acadObj; } + public object ConvertToNativeDisplayable(Base @object) + { + throw new NotImplementedException(); + } + public List ConvertToNative(List objects) { return objects.Select(x => ConvertToNative(x)).ToList(); @@ -534,5 +539,10 @@ public bool CanConvertToNative(Base @object) return false; } } + + public bool CanConvertToNativeDisplayable(Base @object) + { + return false; + } } } diff --git a/Objects/Converters/ConverterBentley/ConverterBentleyShared/ConverterBentley.cs b/Objects/Converters/ConverterBentley/ConverterBentleyShared/ConverterBentley.cs index b8cf66f531..2142e01b61 100644 --- a/Objects/Converters/ConverterBentley/ConverterBentleyShared/ConverterBentley.cs +++ b/Objects/Converters/ConverterBentley/ConverterBentleyShared/ConverterBentley.cs @@ -1,4 +1,4 @@ -using Bentley.DgnPlatformNET; +using Bentley.DgnPlatformNET; using Bentley.DgnPlatformNET.DgnEC; using Bentley.DgnPlatformNET.Elements; using Bentley.EC.Persistence.Query; @@ -62,7 +62,9 @@ public partial class ConverterBentley : ISpeckleConverter public string Name => nameof(ConverterBentley); public string Author => "Arup"; public string WebsiteOrEmail => "https://www.arup.com"; + public IEnumerable GetServicedApplications() => new string[] { BentleyAppName }; + public ProgressReport Report { get; private set; } = new ProgressReport(); public HashSet ConversionErrors { get; private set; } = new HashSet(); public Session Session { get; private set; } @@ -74,8 +76,11 @@ public partial class ConverterBentley : ISpeckleConverter #endif public double UoR { get; private set; } public List ContextObjects { get; set; } = new List(); + public void SetContextObjects(List objects) => ContextObjects = objects; + public void SetPreviousContextObjects(List objects) => throw new NotImplementedException(); + public void SetConverterSettings(object settings) { throw new NotImplementedException("This converter does not have any settings."); @@ -321,6 +326,11 @@ public object ConvertToNative(Base @object) } } + public object ConvertToNativeDisplayable(Base @object) + { + throw new NotImplementedException(); + } + public List ConvertToNative(List objects) { return objects.Select(x => ConvertToNative(x)).ToList(); @@ -391,5 +401,10 @@ public bool CanConvertToNative(Base @object) return false; } } + + public bool CanConvertToNativeDisplayable(Base @object) + { + return false; + } } -} \ No newline at end of file +} diff --git a/Objects/Converters/ConverterCSI/ConverterCSIShared/ConverterCSI.cs b/Objects/Converters/ConverterCSI/ConverterCSIShared/ConverterCSI.cs index 84f008c6c6..f9053b043c 100644 --- a/Objects/Converters/ConverterCSI/ConverterCSIShared/ConverterCSI.cs +++ b/Objects/Converters/ConverterCSI/ConverterCSIShared/ConverterCSI.cs @@ -45,6 +45,7 @@ public partial class ConverterCSI : ISpeckleConverter public Model SpeckleModel { get; set; } public ReceiveMode ReceiveMode { get; set; } + /// /// To know which objects are already in the model. These are *mostly* elements that are in the model before the receive operation starts, but certain names will be added for objects that may be referenced by other elements such as load patterns and load cases. /// The keys are typically GUIDS and the values are exclusively names. It is easier to retrieve names, and names are typically used by the api, however GUIDS are more stable and can't be changed in the user interface. Some items (again load patterns and load combinations) don't have GUIDs so those just store the name value twice. @@ -63,8 +64,11 @@ public partial class ConverterCSI : ISpeckleConverter /// public List PreviousContextObjects { get; set; } = new List(); public Dictionary Settings { get; private set; } = new Dictionary(); + public void SetContextObjects(List objects) => ContextObjects = objects; + public void SetPreviousContextObjects(List objects) => PreviousContextObjects = objects; + public void SetContextDocument(object doc) { Model = (cSapModel)doc; @@ -77,7 +81,7 @@ public void SetContextDocument(object doc) throw new Exception("operation setting was not set before calling converter.SetContextDocument"); if (Settings["operation"] == "receive") - { + { ExistingObjectGuids = GetAllGuids(Model); // TODO: make sure we are setting the load patterns before we import load combinations } @@ -88,6 +92,7 @@ public void SetContextDocument(object doc) else throw new Exception("operation setting was not set to \"send\" or \"receive\""); } + public void SetConverterSettings(object settings) { Settings = settings as Dictionary; @@ -117,7 +122,13 @@ public bool CanConvertToNative(Base @object) case BuiltElements.Brace _: case BuiltElements.Column _: return true; - }; + } + ; + return false; + } + + public bool CanConvertToNativeDisplayable(Base @object) + { return false; } @@ -202,11 +213,14 @@ public object ConvertToNative(Base @object) break; #endregion default: - appObj.Update(status: ApplicationObject.State.Skipped, logItem: $"Skipped not supported type: {@object.GetType()}"); + appObj.Update( + status: ApplicationObject.State.Skipped, + logItem: $"Skipped not supported type: {@object.GetType()}" + ); break; } - // log + // log var reportObj = Report.ReportObjects.ContainsKey(@object.id) ? Report.ReportObjects[@object.id] : null; if (reportObj != null && notes.Count > 0) reportObj.Update(log: notes); @@ -214,6 +228,11 @@ public object ConvertToNative(Base @object) return appObj; } + public object ConvertToNativeDisplayable(Base @object) + { + throw new NotImplementedException(); + } + public List ConvertToNative(List objects) { return objects.Select(x => ConvertToNative(x)).ToList(); @@ -334,64 +353,64 @@ public Base ConvertToSpeckle(object @object) returnObject = LoadPatternToSpeckle(name); Report.Log($"Created Loading Pattern"); break; - //case "ColumnResults": - // returnObject = FrameResultSet1dToSpeckle(name); - // break; - //case "BeamResults": - // returnObject = FrameResultSet1dToSpeckle(name); - // break; - //case "BraceResults": - // returnObject = FrameResultSet1dToSpeckle(name); - // break; - //case "PierResults": - // returnObject = PierResultSet1dToSpeckle(name); - // break; - //case "SpandrelResults": - // returnObject = SpandrelResultSet1dToSpeckle(name); - // break; - //case "GridSys": - // returnObject = GridSysToSpeckle(name); - // break; - //case "Combo": - // returnObject = ComboToSpeckle(name); - // break; - //case "DesignSteel": - // returnObject = DesignSteelToSpeckle(name); - // break; - //case "DeisgnConcrete": - // returnObject = DesignConcreteToSpeckle(name); - // break; - //case "Story": - // returnObject = StoryToSpeckle(name); - // break; - //case "Diaphragm": - // returnObject = DiaphragmToSpeckle(name); - // break; - //case "PierLabel": - // returnObject = PierLabelToSpeckle(name); - // break; - //case "PropAreaSpring": - // returnObject = PropAreaSpringToSpeckle(name); - // break; - //case "PropLineSpring": - // returnObject = PropLineSpringToSpeckle(name); - // break; - //case "PropPointSpring": - // returnObject = PropPointSpringToSpeckle(name); - // break; - //case "SpandrelLabel": - // returnObject = SpandrelLabelToSpeckle(name); - // break; - //case "PropTendon": - // returnObject = PropTendonToSpeckle(name); - // break; - //case "PropLink": - // returnObject = PropLinkToSpeckle(name); - // break; - //default: - // ConversionErrors.Add(new SpeckleException($"Skipping not supported type: {type}")); - // returnObject = null; - // break; + //case "ColumnResults": + // returnObject = FrameResultSet1dToSpeckle(name); + // break; + //case "BeamResults": + // returnObject = FrameResultSet1dToSpeckle(name); + // break; + //case "BraceResults": + // returnObject = FrameResultSet1dToSpeckle(name); + // break; + //case "PierResults": + // returnObject = PierResultSet1dToSpeckle(name); + // break; + //case "SpandrelResults": + // returnObject = SpandrelResultSet1dToSpeckle(name); + // break; + //case "GridSys": + // returnObject = GridSysToSpeckle(name); + // break; + //case "Combo": + // returnObject = ComboToSpeckle(name); + // break; + //case "DesignSteel": + // returnObject = DesignSteelToSpeckle(name); + // break; + //case "DeisgnConcrete": + // returnObject = DesignConcreteToSpeckle(name); + // break; + //case "Story": + // returnObject = StoryToSpeckle(name); + // break; + //case "Diaphragm": + // returnObject = DiaphragmToSpeckle(name); + // break; + //case "PierLabel": + // returnObject = PierLabelToSpeckle(name); + // break; + //case "PropAreaSpring": + // returnObject = PropAreaSpringToSpeckle(name); + // break; + //case "PropLineSpring": + // returnObject = PropLineSpringToSpeckle(name); + // break; + //case "PropPointSpring": + // returnObject = PropPointSpringToSpeckle(name); + // break; + //case "SpandrelLabel": + // returnObject = SpandrelLabelToSpeckle(name); + // break; + //case "PropTendon": + // returnObject = PropTendonToSpeckle(name); + // break; + //case "PropLink": + // returnObject = PropLinkToSpeckle(name); + // break; + //default: + // ConversionErrors.Add(new SpeckleException($"Skipping not supported type: {type}")); + // returnObject = null; + // break; } // send the object out with the same appId that it came in with for updating purposes diff --git a/Objects/Converters/ConverterDynamo/ConverterDynamoShared/ConverterDynamo.cs b/Objects/Converters/ConverterDynamo/ConverterDynamoShared/ConverterDynamo.cs index ff6436d949..4d92af9f00 100644 --- a/Objects/Converters/ConverterDynamo/ConverterDynamoShared/ConverterDynamo.cs +++ b/Objects/Converters/ConverterDynamo/ConverterDynamoShared/ConverterDynamo.cs @@ -1,4 +1,4 @@ -#if REVIT +#if REVIT using Autodesk.Revit.DB; using RD = Revit.Elements; //Dynamo for Revit nodes using Objects.Converter.Revit; @@ -27,7 +27,6 @@ namespace Objects.Converter.Dynamo { public partial class ConverterDynamo : ISpeckleConverter { - #if REVIT2023 public static string AppName = HostApplications.Dynamo.GetVersion(HostAppVersion.vRevit2023); #elif REVIT2022 @@ -186,6 +185,11 @@ public object ConvertToNative(Base @object) } } + public object ConvertToNativeDisplayable(Base @object) + { + throw new NotImplementedException(); + } + public List ConvertToSpeckle(List objects) { return objects.Select(x => ConvertToSpeckle(x)).ToList(); @@ -193,7 +197,8 @@ public List ConvertToSpeckle(List objects) public List ConvertToNative(List objects) { - return objects.Select(x => ConvertToNative(x)).ToList(); ; + return objects.Select(x => ConvertToNative(x)).ToList(); + ; } public bool CanConvertToSpeckle(object @object) @@ -257,12 +262,16 @@ public bool CanConvertToNative(Base @object) } } + public bool CanConvertToNativeDisplayable(Base @object) + { + return false; + } + public void SetContextDocument(object doc) { #if REVIT Doc = (Document)doc; #endif } - } -} \ No newline at end of file +} diff --git a/Objects/Converters/ConverterNavisworks/ConverterNavisworks/ConverterNavisworks.ToNative.cs b/Objects/Converters/ConverterNavisworks/ConverterNavisworks/ConverterNavisworks.ToNative.cs index c8723308fc..b939b552bc 100644 --- a/Objects/Converters/ConverterNavisworks/ConverterNavisworks/ConverterNavisworks.ToNative.cs +++ b/Objects/Converters/ConverterNavisworks/ConverterNavisworks/ConverterNavisworks.ToNative.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Speckle.Core.Models; @@ -9,13 +9,18 @@ public partial class ConverterNavisworks { /// Methods included to satisfy the ISpeckleConverter requirements /// No actual receiving exists - /// + /// /// public object ConvertToNative(Base @object) { throw new NotImplementedException(); } + public object ConvertToNativeDisplayable(Base @object) + { + throw new NotImplementedException(); + } + /// public List ConvertToNative(List objects) { @@ -27,4 +32,9 @@ public bool CanConvertToNative(Base @object) { throw new NotImplementedException(); } + + public bool CanConvertToNativeDisplayable(Base @object) + { + throw new NotImplementedException(); + } } diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/ConverterRevit.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/ConverterRevit.cs index a66b33554e..5dbcf7cc5b 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/ConverterRevit.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/ConverterRevit.cs @@ -472,7 +472,7 @@ public object ConvertToNative(Base @base) receivedObjectsCache.AddConvertedObjects(@base, new List { element }); break; } - + return nativeObject; } @@ -717,6 +717,11 @@ public object ConvertToNativeObject(Base @object) } } + public object ConvertToNativeDisplayable(Base @object) + { + throw new NotImplementedException(); + } + public List ConvertToSpeckle(List objects) => objects.Select(ConvertToSpeckle).ToList(); public List ConvertToNative(List objects) => objects.Select(ConvertToNative).ToList(); @@ -848,5 +853,10 @@ public bool CanConvertToNative(Base @object) _ => false, }; } + + public bool CanConvertToNativeDisplayable(Base @object) + { + return false; + } } } diff --git a/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.cs b/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.cs index 09718f1f7a..eca5ca61cf 100644 --- a/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.cs +++ b/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.cs @@ -556,6 +556,11 @@ public object ConvertToNative(Base @object) return rhinoObj; } + public object ConvertToNativeDisplayable(Base @object) + { + throw new NotImplementedException(); + } + public List ConvertToNative(List objects) { return objects.Select(x => ConvertToNative(x)).ToList(); @@ -669,4 +674,9 @@ public bool CanConvertToNative(Base @object) return false; } } + + public bool CanConvertToNativeDisplayable(Base @object) + { + return false; + } } diff --git a/Objects/Converters/ConverterTeklaStructures/ConverterTeklaStructuresShared/ConverterTeklaStructures.cs b/Objects/Converters/ConverterTeklaStructures/ConverterTeklaStructuresShared/ConverterTeklaStructures.cs index 0243f4d28e..f6dd9fa66c 100644 --- a/Objects/Converters/ConverterTeklaStructures/ConverterTeklaStructuresShared/ConverterTeklaStructures.cs +++ b/Objects/Converters/ConverterTeklaStructures/ConverterTeklaStructuresShared/ConverterTeklaStructures.cs @@ -11,7 +11,6 @@ using BE = Objects.BuiltElements; using GE = Objects.Geometry; - namespace Objects.Converter.TeklaStructures { public partial class ConverterTeklaStructures : ISpeckleConverter @@ -45,6 +44,7 @@ public void SetContextDocument(object doc) { Model = (Model)doc; } + /// /// To know which other objects are being converted, in order to sort relationships between them. /// For example, elements that have children use this to determine whether they should send their children out or not. @@ -94,8 +94,14 @@ public bool CanConvertToNative(Base @object) // return true; default: return false; - //_ => (@object as ModelObject).IsElementSupported() - }; + //_ => (@object as ModelObject).IsElementSupported() + } + ; + } + + public bool CanConvertToNativeDisplayable(Base @object) + { + return false; } public bool CanConvertToSpeckle(object @object) @@ -123,8 +129,9 @@ public bool CanConvertToSpeckle(object @object) return true; default: return false; - //_ => (@object as ModelObject).IsElementSupported() - }; + //_ => (@object as ModelObject).IsElementSupported() + } + ; } public object ConvertToNative(Base @object) @@ -143,7 +150,8 @@ public object ConvertToNative(Base @object) { foreach (var displayAlias in DefaultTraversal.displayValuePropAliases) { - if (@base[displayAlias] is not List meshes) continue; + if (@base[displayAlias] is not List meshes) + continue; MeshToNative(@base, meshes); } @@ -179,11 +187,15 @@ public object ConvertToNative(Base @object) } } + public object ConvertToNativeDisplayable(Base @object) + { + throw new NotImplementedException(); + } + public List ConvertToNative(List objects) => objects.Select(ConvertToNative).ToList(); public Base ConvertToSpeckle(object @object) { - Base returnObject = null; switch (@object) { @@ -228,10 +240,11 @@ public Base ConvertToSpeckle(object @object) Report.Log($"Created Fitting"); break; default: - ConversionErrors.Add(new Exception($"Skipping not supported type: {@object.GetType()}{GetElemInfo(@object)}")); + ConversionErrors.Add( + new Exception($"Skipping not supported type: {@object.GetType()}{GetElemInfo(@object)}") + ); returnObject = null; break; - } return returnObject; } From b1093839c67c3f5d07e4289707c32804394cf346 Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Wed, 16 Aug 2023 13:52:42 +0100 Subject: [PATCH 05/57] Jrm/core/security analysers (#2848) * build: Added security analysers as errors * Resolved security violations --------- Co-authored-by: Alan Rynne --- .../Entry/SpeckleAutocadCommand.cs | 2 + ConnectorCSI/ConnectorCSIShared/cPlugin.cs | 4 +- .../ConnectorDynamo/ReceiveNode/Receive.cs | 2 +- .../Extras/Utilities.cs | 25 +++----- .../ConnectorRevit/Entry/SchedulerCommand.cs | 12 ++-- .../Entry/SpeckleRevitCommand.cs | 62 +++++++------------ .../MainForm.cs | 2 + Core/Core/Credentials/AccountManager.cs | 10 +-- Core/Core/Helpers/Http.cs | 1 + Core/Core/Logging/Setup.cs | 6 -- Core/IntegrationTests/Fixtures.cs | 7 ++- .../AvaloniaHwndHost/UnmanagedMethods.cs | 2 + Directory.Build.props | 30 ++++++++- .../ConverterAutocadCivil.cs | 2 +- .../Partial Classes/ConvertGeometry.cs | 2 +- 15 files changed, 84 insertions(+), 85 deletions(-) diff --git a/ConnectorAutocadCivil/ConnectorAutocadCivil/Entry/SpeckleAutocadCommand.cs b/ConnectorAutocadCivil/ConnectorAutocadCivil/Entry/SpeckleAutocadCommand.cs index 86f3493777..895a19ce5e 100644 --- a/ConnectorAutocadCivil/ConnectorAutocadCivil/Entry/SpeckleAutocadCommand.cs +++ b/ConnectorAutocadCivil/ConnectorAutocadCivil/Entry/SpeckleAutocadCommand.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using System.Runtime.InteropServices; using System.Threading; @@ -32,6 +33,7 @@ public class SpeckleAutocadCommand { #region Avalonia parent window [DllImport("user32.dll", SetLastError = true)] + [SuppressMessage("Security", "CA5392:Use DefaultDllImportSearchPaths attribute for P/Invokes")] static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr value); const int GWL_HWNDPARENT = -8; #endregion diff --git a/ConnectorCSI/ConnectorCSIShared/cPlugin.cs b/ConnectorCSI/ConnectorCSIShared/cPlugin.cs index a3be3146fe..0f9c021889 100644 --- a/ConnectorCSI/ConnectorCSIShared/cPlugin.cs +++ b/ConnectorCSI/ConnectorCSIShared/cPlugin.cs @@ -117,7 +117,7 @@ public void Main(ref cSapModel SapModel, ref cPluginCallback ISapPlugin) } catch (Exception e) { - throw e; + throw; ISapPlugin.Finish(0); //return; } @@ -127,4 +127,4 @@ public void Main(ref cSapModel SapModel, ref cPluginCallback ISapPlugin) } -} \ No newline at end of file +} diff --git a/ConnectorDynamo/ConnectorDynamo/ReceiveNode/Receive.cs b/ConnectorDynamo/ConnectorDynamo/ReceiveNode/Receive.cs index 99c5e5d0e0..316f24c522 100644 --- a/ConnectorDynamo/ConnectorDynamo/ReceiveNode/Receive.cs +++ b/ConnectorDynamo/ConnectorDynamo/ReceiveNode/Receive.cs @@ -455,7 +455,7 @@ internal void InitializeReceiver() Warning(exceptionMessage); Message = exceptionMessage.Contains("don't have access") ? "Not authorised" : "Error"; ReceiveEnabled = false; - throw e; + throw; } } diff --git a/ConnectorGrasshopper/ConnectorGrasshopperShared/Extras/Utilities.cs b/ConnectorGrasshopper/ConnectorGrasshopperShared/Extras/Utilities.cs index 2a429647c1..965152c338 100644 --- a/ConnectorGrasshopper/ConnectorGrasshopperShared/Extras/Utilities.cs +++ b/ConnectorGrasshopper/ConnectorGrasshopperShared/Extras/Utilities.cs @@ -203,25 +203,18 @@ public static List DataTreeToNestedLists( private static void RecurseTreeToList(List parent, List path, int pathIndex, List objects) { - try - { - var listIndex = path[pathIndex]; //there should be a list at this index inside this parent list - - parent = EnsureHasSublistAtIndex(parent, listIndex); - var sublist = parent[listIndex] as List; - //it's the last index of the path => the last sublist => add objects - if (pathIndex == path.Count - 1) - { - sublist.AddRange(objects); - return; - } + var listIndex = path[pathIndex]; //there should be a list at this index inside this parent list - RecurseTreeToList(sublist, path, pathIndex + 1, objects); - } - catch (Exception ex) + parent = EnsureHasSublistAtIndex(parent, listIndex); + var sublist = parent[listIndex] as List; + //it's the last index of the path => the last sublist => add objects + if (pathIndex == path.Count - 1) { - throw ex; + sublist.AddRange(objects); + return; } + + RecurseTreeToList(sublist, path, pathIndex + 1, objects); } /// diff --git a/ConnectorRevit/ConnectorRevit/Entry/SchedulerCommand.cs b/ConnectorRevit/ConnectorRevit/Entry/SchedulerCommand.cs index b657989f02..288c248d47 100644 --- a/ConnectorRevit/ConnectorRevit/Entry/SchedulerCommand.cs +++ b/ConnectorRevit/ConnectorRevit/Entry/SchedulerCommand.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using Autodesk.Revit.Attributes; using Autodesk.Revit.DB; @@ -13,14 +14,15 @@ namespace Speckle.ConnectorRevit.Entry public class SchedulerCommand : IExternalCommand { [DllImport("user32.dll", SetLastError = true)] + [SuppressMessage("Security", "CA5392:Use DefaultDllImportSearchPaths attribute for P/Invokes")] static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr value); + const int GWL_HWNDPARENT = -8; internal static UIApplication uiapp; public static ConnectorBindingsRevit Bindings { get; set; } - public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements) { uiapp = commandData.Application; @@ -31,11 +33,7 @@ public Result Execute(ExternalCommandData commandData, ref string message, Eleme public static void CreateOrFocusSpeckle() { - - var scheduler = new Scheduler - { - DataContext = new SchedulerViewModel(Bindings) - }; + var scheduler = new Scheduler { DataContext = new SchedulerViewModel(Bindings) }; scheduler.Show(); @@ -45,8 +43,6 @@ public static void CreateOrFocusSpeckle() var hwnd = scheduler.PlatformImpl.Handle.Handle; SetWindowLongPtr(hwnd, GWL_HWNDPARENT, parentHwnd); } - } } - } diff --git a/ConnectorRevit/ConnectorRevit/Entry/SpeckleRevitCommand.cs b/ConnectorRevit/ConnectorRevit/Entry/SpeckleRevitCommand.cs index f787de47ef..bc3391d094 100644 --- a/ConnectorRevit/ConnectorRevit/Entry/SpeckleRevitCommand.cs +++ b/ConnectorRevit/ConnectorRevit/Entry/SpeckleRevitCommand.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using System.Threading; using Autodesk.Revit.Attributes; @@ -18,42 +19,41 @@ namespace Speckle.ConnectorRevit.Entry [Transaction(TransactionMode.Manual)] public class SpeckleRevitCommand : IExternalCommand { - public static bool UseDockablePanel = true; //window stuff [DllImport("user32.dll", SetLastError = true)] + [SuppressMessage("Security", "CA5392:Use DefaultDllImportSearchPaths attribute for P/Invokes")] static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr value); + const int GWL_HWNDPARENT = -8; public static Window MainWindow { get; private set; } private static Avalonia.Application AvaloniaApp { get; set; } + //end window stuff public static ConnectorBindingsRevit Bindings { get; set; } private static Panel _panel { get; set; } - internal static DockablePaneId PanelId = new DockablePaneId(new Guid("{0A866FB8-8FD5-4DE8-B24B-56F4FA5B0836}")); - public static void InitAvalonia() { BuildAvaloniaApp().SetupWithoutStarting(); } - public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure() - .UsePlatformDetect() - .With(new SkiaOptions { MaxGpuResourceSizeBytes = 8096000 }) - .With(new Win32PlatformOptions { AllowEglInitialization = true, EnableMultitouch = false }) - .LogToTrace() - .UseReactiveUI(); - - + public static AppBuilder BuildAvaloniaApp() => + AppBuilder + .Configure() + .UsePlatformDetect() + .With(new SkiaOptions { MaxGpuResourceSizeBytes = 8096000 }) + .With(new Win32PlatformOptions { AllowEglInitialization = true, EnableMultitouch = false }) + .LogToTrace() + .UseReactiveUI(); public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements) { - if (UseDockablePanel) { try @@ -70,8 +70,6 @@ public Result Execute(ExternalCommandData commandData, ref string message, Eleme else CreateOrFocusSpeckle(); - - return Result.Succeeded; } @@ -95,10 +93,7 @@ internal static void RegisterPane() { //Register dockable panel var viewModel = new MainViewModel(Bindings); - _panel = new Panel - { - DataContext = viewModel - }; + _panel = new Panel { DataContext = viewModel }; App.AppInstance.RegisterDockablePane(PanelId, "Speckle", _panel); _panel.Init(); } @@ -111,24 +106,23 @@ internal static void RegisterPane() TaskDialog mainDialog = new TaskDialog("Dockable Panel Issue"); mainDialog.MainInstruction = "Dockable Panel Issue"; mainDialog.MainContent = - "Revit cannot properly register Dockable Panels when launched by double-clicking a Revit file. " - + "Please close and re-open Revit without launching a file OR open/create a new project to trigger the Speckle panel registration."; - + "Revit cannot properly register Dockable Panels when launched by double-clicking a Revit file. " + + "Please close and re-open Revit without launching a file OR open/create a new project to trigger the Speckle panel registration."; // Set footer text. Footer text is usually used to link to the help document. mainDialog.FooterText = - "" - + "Click here for more info"; + "" + + "Click here for more info"; mainDialog.Show(); - } } catch (Exception ex) { SpeckleLog.Logger.Fatal(ex, "Failed to load Speckle command for host app"); var td = new TaskDialog("Error"); - td.MainContent = $"Oh no! Something went wrong while loading Speckle, please report it on the forum:\n{ex.Message}"; + td.MainContent = + $"Oh no! Something went wrong while loading Speckle, please report it on the forum:\n{ex.Message}"; td.AddCommandLink(TaskDialogCommandLinkId.CommandLink1, "Report issue on our Community Forum"); TaskDialogResult tResult = td.Show(); @@ -138,31 +132,24 @@ internal static void RegisterPane() Process.Start("https://speckle.community/"); } } - } public static void CreateOrFocusSpeckle(bool showWindow = true) { try { - if (MainWindow == null) { var viewModel = new MainViewModel(Bindings); - MainWindow = new MainWindow - { - DataContext = viewModel - }; + MainWindow = new MainWindow { DataContext = viewModel }; //massive hack: we start the avalonia main loop and stop it immediately (since it's thread blocking) //to avoid an annoying error when closing revit var cts = new CancellationTokenSource(); cts.CancelAfter(100); AvaloniaApp.Run(cts.Token); - } - if (showWindow) { MainWindow.Show(); @@ -185,7 +172,8 @@ public static void CreateOrFocusSpeckle(bool showWindow = true) { SpeckleLog.Logger.Fatal(ex, "Failed to create main window"); var td = new TaskDialog("Error"); - td.MainContent = $"Oh no! Something went wrong while loading Speckle, please report it on the forum:\n{ex.Message}"; + td.MainContent = + $"Oh no! Something went wrong while loading Speckle, please report it on the forum:\n{ex.Message}"; td.AddCommandLink(TaskDialogCommandLinkId.CommandLink1, "Report issue on our Community Forum"); TaskDialogResult tResult = td.Show(); @@ -201,11 +189,5 @@ private static void AppMain(Avalonia.Application app, string[] args) { AvaloniaApp = app; } - - - - - } - } diff --git a/ConnectorTeklaStructures/ConnectorTeklaStructuresShared/MainForm.cs b/ConnectorTeklaStructures/ConnectorTeklaStructuresShared/MainForm.cs index 845f8075de..a66ede69eb 100644 --- a/ConnectorTeklaStructures/ConnectorTeklaStructuresShared/MainForm.cs +++ b/ConnectorTeklaStructures/ConnectorTeklaStructuresShared/MainForm.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -21,6 +22,7 @@ public partial class MainForm : PluginFormBase //window owner call [DllImport("user32.dll", SetLastError = true)] + [SuppressMessage("Security", "CA5392:Use DefaultDllImportSearchPaths attribute for P/Invokes")] static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr value); const int GWL_HWNDPARENT = -8; diff --git a/Core/Core/Credentials/AccountManager.cs b/Core/Core/Credentials/AccountManager.cs index 21fcd0c744..76c706360c 100644 --- a/Core/Core/Credentials/AccountManager.cs +++ b/Core/Core/Credentials/AccountManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -536,9 +536,7 @@ private static async Task GetToken(string accessCode, str { try { - ServicePointManager.SecurityProtocol = - SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; - var client = Http.GetHttpProxyClient(); + using var client = Http.GetHttpProxyClient(); var body = new { @@ -566,9 +564,7 @@ private static async Task GetRefreshedToken(string refres { try { - ServicePointManager.SecurityProtocol = - SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; - var client = Http.GetHttpProxyClient(); + using var client = Http.GetHttpProxyClient(); var body = new { diff --git a/Core/Core/Helpers/Http.cs b/Core/Core/Helpers/Http.cs index 1d2d60c942..e77b9057c3 100644 --- a/Core/Core/Helpers/Http.cs +++ b/Core/Core/Helpers/Http.cs @@ -212,6 +212,7 @@ public class SpeckleHttpClientHandler : HttpClientHandler public SpeckleHttpClientHandler(IEnumerable? delay = null) { _delay = delay ?? Http.DefaultDelay(); + CheckCertificateRevocationList = true; } protected override async Task SendAsync( diff --git a/Core/Core/Logging/Setup.cs b/Core/Core/Logging/Setup.cs index 66154603c5..01edc37053 100644 --- a/Core/Core/Logging/Setup.cs +++ b/Core/Core/Logging/Setup.cs @@ -63,12 +63,6 @@ public static void Init(string versionedHostApplication, string hostApplication) //start mutex so that Manager can detect if this process is running mutex = new Mutex(false, "SpeckleConnector-" + hostApplication); -#if !NETSTANDARD1_5_OR_GREATER - //needed by older .net frameworks, eg Revit 2019 - ServicePointManager.SecurityProtocol = - SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12; -#endif - SpeckleLog.Initialize(hostApplication, versionedHostApplication); foreach (var account in AccountManager.GetAccounts()) diff --git a/Core/IntegrationTests/Fixtures.cs b/Core/IntegrationTests/Fixtures.cs index 4f9f8f9b87..cfe6e59a21 100644 --- a/Core/IntegrationTests/Fixtures.cs +++ b/Core/IntegrationTests/Fixtures.cs @@ -32,7 +32,10 @@ public static async Task SeedUser() user["password"] = "12ABC3456789DEF0GHO"; user["name"] = $"{seed.Substring(0, 5)} Name"; - var httpClient = new HttpClient(new HttpClientHandler { AllowAutoRedirect = false }); + using var httpClient = new HttpClient( + new HttpClientHandler { AllowAutoRedirect = false, CheckCertificateRevocationList = true } + ); + httpClient.BaseAddress = new Uri(Server.url); string redirectUrl; @@ -85,7 +88,7 @@ await tokenResponse.Content.ReadAsStringAsync().ConfigureAwait(false) }, serverInfo = Server }; - var client = new Client(acc); + using var client = new Client(acc); var user1 = await client.ActiveUserGet().ConfigureAwait(false); acc.userInfo.id = user1.id; diff --git a/DesktopUI2/AvaloniaHwndHost/UnmanagedMethods.cs b/DesktopUI2/AvaloniaHwndHost/UnmanagedMethods.cs index 0399f832a9..55a13a9b2c 100644 --- a/DesktopUI2/AvaloniaHwndHost/UnmanagedMethods.cs +++ b/DesktopUI2/AvaloniaHwndHost/UnmanagedMethods.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; namespace AvaloniaHwndHost; @@ -6,5 +7,6 @@ namespace AvaloniaHwndHost; internal static class UnmanagedMethods { [DllImport("user32.dll")] + [SuppressMessage("Security", "CA5392:Use DefaultDllImportSearchPaths attribute for P/Invokes")] public static extern bool SetParent(IntPtr hWnd, IntPtr hWndNewParent); } diff --git a/Directory.Build.props b/Directory.Build.props index aab3972c23..ecb796b764 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -14,7 +14,7 @@ - latest + 11 true latest-AllEnabledByDefault @@ -22,8 +22,10 @@ + CS1591;CS1573;CS1572;CS1570;CS1587;CS1574; CS1711;CS1734; + CS8618;CS8602;CS8600; CS1998; IDE0007; CS0618; CA1054; @@ -32,6 +34,32 @@ NU1701; $(NoWarn) + + + + CA1416; CA1417; CA1418; CA1831; CA2013; CA2014; CA2015; CA2017; + CA2018; CA2200; CA2252; CA2247; CA2255; CA2256; CA2257; CA2258; + + CA1420; CA1422; CA2259; CA2260; + + CA2100; CA2119; CA2153; CA2300; CA2301; CA2302; CA2305; + CA2310; CA2311; CA2312; CA2315; CA2321; CA2322; CA2326; CA2327; + CA2328; CA2329; CA2330; CA2350; CA2351; CA2352; CA2353; CA2354; + CA2355; CA2356; CA2361; CA2362; CA3001; CA3002; CA3003; CA3004; + CA3006; CA3007; CA3008; CA3009; CA3010; CA3011; CA3012; CA3061; + CA3076; CA3077; CA3147; CA5350; CA5351; CA5358; CA5359; + CA5360; CA5361; CA5362; CA5363; CA5364; CA5365; CA5366; CA5367; + CA5368; CA5370; CA5371; CA5372; CA5373; CA5374; CA5375; + CA5376; CA5377; CA5378; CA5379; CA5380; CA5381; CA5382; CA5383; + CA5384; CA5385; CA5386; CA5387; CA5388; CA5389; CA5390; CA5391; + CA5392; CA5393; CA5395; CA5396; CA5397; CA5398; CA5399; + CA5400; CA5401; CA5402; CA5403; CA5404; CA5405 + + + + + + true diff --git a/Objects/Converters/ConverterAutocadCivil/ConverterAutocadCivilShared/ConverterAutocadCivil.cs b/Objects/Converters/ConverterAutocadCivil/ConverterAutocadCivilShared/ConverterAutocadCivil.cs index bf01ef766b..102a00f57c 100644 --- a/Objects/Converters/ConverterAutocadCivil/ConverterAutocadCivilShared/ConverterAutocadCivil.cs +++ b/Objects/Converters/ConverterAutocadCivil/ConverterAutocadCivilShared/ConverterAutocadCivil.cs @@ -226,7 +226,7 @@ public Base ConvertToSpeckle(object @object) { //Update report because AS object type Report.UpdateReportObject(reportObj); - throw ex; + throw; } break; diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertGeometry.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertGeometry.cs index 4ede4b2905..7920d9ecd0 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertGeometry.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertGeometry.cs @@ -392,7 +392,7 @@ public DB.Curve CurveToNative(Curve speckleCurve) catch (Exception e) { if (e is Autodesk.Revit.Exceptions.ArgumentException) - throw e; // prob a closed, periodic curve + throw; // prob a closed, periodic curve return null; } } From 5770053061c087f67019b7439db0b3d999a07202 Mon Sep 17 00:00:00 2001 From: connorivy <43247197+connorivy@users.noreply.github.com> Date: Thu, 17 Aug 2023 03:21:54 -0500 Subject: [PATCH 06/57] Fix(Revit) : receive curtain panels as nested elements (#2855) * add curtain panels as nested elements * add logic for view specific case --------- Co-authored-by: Connor Ivy --- .../Partial Classes/ConvertFamilyInstance.cs | 9 ++- .../Partial Classes/ConvertGroup.cs | 19 +++-- .../Partial Classes/ConvertWall.cs | 69 ++++++------------- 3 files changed, 38 insertions(+), 59 deletions(-) diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertFamilyInstance.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertFamilyInstance.cs index fb4b9c1760..d5dad4a58c 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertFamilyInstance.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertFamilyInstance.cs @@ -50,9 +50,12 @@ public Base FamilyInstanceToSpeckle(DB.FamilyInstance revitFi, out List return null; else if (revitFi is Mullion mullion) { - var direction = ((DB.Line)mullion.LocationCurve).Direction; - // TODO: add support for more severly sloped mullions. This isn't very robust at the moment - isUGridLine = Math.Abs(direction.X) > Math.Abs(direction.Y); + if (mullion.LocationCurve is DB.Line locationLine && locationLine.Direction != null) + { + var direction = locationLine.Direction; + // TODO: add support for more severly sloped mullions. This isn't very robust at the moment + isUGridLine = Math.Abs(direction.X) > Math.Abs(direction.Y); + } } else //TODO: sort these so we consistently get sub-elements from the wall element in case also sub-elements are sent diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertGroup.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertGroup.cs index 6431a2e323..1beaa695e1 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertGroup.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertGroup.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Autodesk.Revit.DB; @@ -21,16 +21,22 @@ public Base GroupToSpeckle(Group revitGroup) @base["name"] = revitGroup.Name; @base["type"] = "group"; @base["level"] = ConvertAndCacheLevel(revitGroup, BuiltInParameter.GROUP_LEVEL); - + + AddHostedDependentElements(revitGroup, @base, elIdsToConvert.ToList()); + return @base; + } + + private void AddHostedDependentElements(Element revitElement, Base @base, List hostedIds) + { // loop backward through the list so you can remove elements as you go through it - for (int i = elIdsToConvert.Count - 1; i >= 0; i--) + for (int i = hostedIds.Count - 1; i >= 0; i--) { - var element = Doc.GetElement(elIdsToConvert[i]); + var element = Doc.GetElement(hostedIds[i]); // if it's already part of the selection, remove this element from the list of element // we can't prevent the other element (with same id) to be converted, like we do for hosted elements if (ContextObjects.ContainsKey(element.UniqueId)) - elIdsToConvert.RemoveAt(i); + hostedIds.RemoveAt(i); // otherwise, add the elements to the ContextObjects before converting them because a group // may contain a wall that has a window, so we still want the window to search through the contextObjects // and recognize that it's host, the wall, is listed in there and not to convert itself @@ -38,8 +44,7 @@ public Base GroupToSpeckle(Group revitGroup) ContextObjects.Add(element.UniqueId, new ApplicationObject(null, null) { applicationId = element.UniqueId }); } - GetHostedElementsFromIds(@base, revitGroup, elIdsToConvert, out List notes); - return @base; + GetHostedElementsFromIds(@base, revitElement, hostedIds, out List notes); } } } diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertWall.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertWall.cs index fdbb980982..f5d82c6f43 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertWall.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertWall.cs @@ -180,7 +180,7 @@ public Base WallToSpeckle(DB.Wall revitWall, out List notes) //CreateVoids(revitWall, speckleWall); - if (revitWall.CurtainGrid == null) + if (revitWall.CurtainGrid is not CurtainGrid grid) { if (revitWall.IsStackedWall) { @@ -195,21 +195,14 @@ public Base WallToSpeckle(DB.Wall revitWall, out List notes) } else { - // curtain walls have two meshes, one for panels and one for mullions - // adding mullions as sub-elements so they can be correctly displayed in viewers etc - var (panelsMesh, mullionsMesh) = GetCurtainWallDisplayMesh(revitWall); - speckleWall["renderMaterial"] = new Other.RenderMaterial() { opacity = 0.2, diffuse = System.Drawing.Color.AliceBlue.ToArgb() }; - speckleWall.displayValue = panelsMesh; - - var elements = new List(); - if (mullionsMesh.Count > 0) //Only add mullions object if they have meshes - { - elements.Add(new Base - { - ["@displayValue"] = mullionsMesh - }); - } - speckleWall.elements = elements; + AddHostedDependentElements( + revitWall, + speckleWall, + GetWallSubElementsInView(BuiltInCategory.OST_CurtainWallMullions) ?? grid.GetMullionIds().ToList()); + AddHostedDependentElements( + revitWall, + speckleWall, + GetWallSubElementsInView(BuiltInCategory.OST_CurtainWallPanels) ?? grid.GetPanelIds().ToList()); } GetAllRevitParamsAndIds(speckleWall, revitWall, new List @@ -228,43 +221,21 @@ public Base WallToSpeckle(DB.Wall revitWall, out List notes) return speckleWall; } - private (List, List) GetCurtainWallDisplayMesh(DB.Wall wall) - { - var grid = wall.CurtainGrid; - - var meshPanels = GetWallSubElementMeshes(grid.GetPanelIds(), BuiltInCategory.OST_CurtainWallPanels); - var meshMullions = GetWallSubElementMeshes(grid.GetMullionIds(), BuiltInCategory.OST_CurtainWallMullions); - - return (meshPanels, meshMullions); - } - - private List GetWallSubElementMeshes(IEnumerable elementIds, BuiltInCategory category) + private List? GetWallSubElementsInView(BuiltInCategory category) { - var meshes = new List(); - HashSet idsInView = null; - if (ViewSpecificOptions != null) + if (ViewSpecificOptions == null) { - using var filter = new ElementCategoryFilter(category); - using var collector = new FilteredElementCollector(Doc, ViewSpecificOptions.View.Id) - .WhereElementIsNotElementType() - .WherePasses(filter); - - idsInView = new HashSet(collector.ToElementIds().Select(id => id.IntegerValue)); + return null; } - foreach (var id in elementIds) - { - if (idsInView != null && !idsInView.Contains(id.IntegerValue)) - { - continue; - } - //TODO: sort these so we consistently get sub-elements from the wall element in case also individual sub-elements are sent - if (SubelementIds.Contains(id)) - continue; - SubelementIds.Add(id); - meshes.AddRange(GetElementDisplayValue(Doc.GetElement(id))); - } - return meshes; + using var filter = new ElementCategoryFilter(category); + using var collector = new FilteredElementCollector(Doc, ViewSpecificOptions.View.Id); + + return collector + .WhereElementIsNotElementType() + .WherePasses(filter) + .ToElementIds() + .ToList(); } //this is to prevent duplicated panels & mullions from being sent in curtain walls From d6e717555443fb2d8ede786fc0097558c98f0c68 Mon Sep 17 00:00:00 2001 From: Chuong Ho <31106432+chuongmep@users.noreply.github.com> Date: Thu, 17 Aug 2023 22:37:03 +0800 Subject: [PATCH 07/57] feat(revit) : adds non-length units to parameters (#2773) * add support parameter unit * add more catch * Add detail unit value parameter define * cover catch with revit lower version * allow null units * add model units, save time send data * support v20 * support parameter units version < 2021 * fix conflict namespace * fix conflict namespace unit * removes builtelelements units class and model units method * removes unnecessary usings * removes unnecessary usings * fixes merge conflicts * removes obsolete tag --------- Co-authored-by: Claire Kuang --- .../ConverterRevitShared/ConversionUtils.cs | 73 ++++++++++++++++--- .../ConvertAnalyticalSurface.cs | 2 +- .../Partial Classes/ConvertProfileWall.cs | 2 +- .../Partial Classes/ConvertRailing.cs | 2 +- .../Partial Classes/ConvertRoof.cs | 2 +- .../Objects/BuiltElements/Revit/Parameter.cs | 4 +- 6 files changed, 70 insertions(+), 15 deletions(-) diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/ConversionUtils.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/ConversionUtils.cs index 22fa507797..6ade95f84e 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/ConversionUtils.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/ConversionUtils.cs @@ -17,9 +17,6 @@ using Speckle.Core.Models.Extensions; using Speckle.Core.Models.GraphTraversal; using DB = Autodesk.Revit.DB; -using Duct = Objects.BuiltElements.Duct; -using ElementType = Autodesk.Revit.DB.ElementType; -using Floor = Objects.BuiltElements.Floor; using Level = Objects.BuiltElements.Level; using Line = Objects.Geometry.Line; using OSG = Objects.Structural.Geometry; @@ -382,7 +379,8 @@ private static Parameter ParameterToSpeckle( isShared = rp.IsShared, isReadOnly = rp.IsReadOnly, isTypeParameter = isTypeParameter, - applicationUnitType = definition.GetUnityTypeString() //eg UT_Length + applicationUnitType = definition.GetUnityTypeString(), //eg UT_Length + units = GetSymbolUnit(rp), }; sp.value = GetParameterValue(rp, definition, out var appUnit, unitsOverride, cache); @@ -439,7 +437,65 @@ private static object GetParameterValue( } #endregion - + + /// + /// Get the symbol unit of the parameter : eg. mm, m, cm, etc. + /// + /// the parameter of revit + /// + public static string GetSymbolUnit(DB.Parameter parameter) + { + string symbol = string.Empty; +#if REVIT2020 + try + { + DisplayUnitType forgeTypeId = parameter.DisplayUnitType; + IList validSymbols = FormatOptions.GetValidUnitSymbols(forgeTypeId); + if (validSymbols.Count > 0) + { + var unitSymbolTypes = validSymbols.Where(x => x != UnitSymbolType.UST_NONE).ToArray(); + if (unitSymbolTypes.Any()) + { + foreach (DB.UnitSymbolType symbolId in unitSymbolTypes) + { + symbol = LabelUtils.GetLabelFor(symbolId); + return symbol; + } + } + } + } + catch (Exception e) + { + // ignore with catch symbol + } +#else + try + { + ForgeTypeId forgeTypeId = parameter.GetUnitTypeId(); + if (FormatOptions.CanHaveSymbol(forgeTypeId)) + { + IList validSymbols = FormatOptions.GetValidSymbols(forgeTypeId); + if (validSymbols.Count > 0) + { + IEnumerable typeId = validSymbols.Where(x => !x.Empty()); + if (typeId.Any()) + { + foreach (DB.ForgeTypeId symbolId in typeId) + { + symbol = LabelUtils.GetLabelForSymbol(symbolId); + } + } + } + } + } + catch (Exception e) + { + // ignore with catch symbol + } +#endif + return symbol; + } + /// /// /// @@ -497,7 +553,7 @@ public void SetInstanceParameters(Element revitElement, Base speckleElement, Lis var rp = revitParameterById.ContainsKey(spk.Key) ? revitParameterById[spk.Key] : revitParameterByName[spk.Key]; - TrySetParam(rp, sp.value, sp.units, sp.applicationUnit); + TrySetParam(rp, sp.value, applicationUnit: sp.applicationUnit); } } @@ -595,7 +651,7 @@ private static string GetParamInternalName(DB.Parameter rp) // else // return (rp.Definition as InternalDefinition).BuiltInParameter != ; //} - + private void TrySetParam(DB.Element elem, BuiltInParameter bip, DB.Element value) { var param = elem.get_Parameter(bip); @@ -631,7 +687,7 @@ private void TrySetParam(DB.Element elem, BuiltInParameter bip, bool value) // } //} - + private void TrySetParam(DB.Element elem, BuiltInParameter bip, object value, string units = "") { var param = elem.get_Parameter(bip); @@ -642,7 +698,6 @@ private void TrySetParam(DB.Element elem, BuiltInParameter bip, object value, st TrySetParam(param, value, units); } - /// /// Queries a Revit Document for phases by the given name. /// diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertAnalyticalSurface.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertAnalyticalSurface.cs index fa52df7636..9de0e70d51 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertAnalyticalSurface.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertAnalyticalSurface.cs @@ -117,7 +117,7 @@ public ApplicationObject AnalyticalSurfaceToNative(Element2D speckleElement) // make sure that the bottom of the floor remains in the same location var newTypeDepth = GetParamValue(elementType, BuiltInParameter.FLOOR_ATTR_DEFAULT_THICKNESS_PARAM); - TrySetParam(physicalMember, BuiltInParameter.FLOOR_HEIGHTABOVELEVEL_PARAM, currentHeightOffset + (newTypeDepth - currentTypeDepth)); + TrySetParam(physicalMember, BuiltInParameter.FLOOR_HEIGHTABOVELEVEL_PARAM, currentHeightOffset + (newTypeDepth - currentTypeDepth),""); } } } diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertProfileWall.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertProfileWall.cs index ddcd266ffd..7383cf88f8 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertProfileWall.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertProfileWall.cs @@ -61,7 +61,7 @@ public ApplicationObject ProfileWallToNative(RevitProfileWall speckleRevitWall) TrySetParam(revitWall, BuiltInParameter.WALL_BASE_CONSTRAINT, level); var offset = minZ - level.Elevation; - TrySetParam(revitWall, BuiltInParameter.WALL_BASE_OFFSET, offset); + TrySetParam(revitWall, BuiltInParameter.WALL_BASE_OFFSET, offset,""); if (revitWall.WallType.Name != wallType.Name) revitWall.ChangeTypeId(wallType.Id); diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertRailing.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertRailing.cs index ae9cddaea9..e7943af50a 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertRailing.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertRailing.cs @@ -63,7 +63,7 @@ public ApplicationObject RailingToNative(BuiltElements.Revit.RevitRailing speckl var topRailType = GetElementType(speckleRailing.topRail, appObj, out bool isTopRailExactMatch); if (GetParamValue(railingType, BuiltInParameter.RAILING_SYSTEM_HAS_TOP_RAIL) == 0) - TrySetParam(railingType, BuiltInParameter.RAILING_SYSTEM_HAS_TOP_RAIL, 1); + TrySetParam(railingType, BuiltInParameter.RAILING_SYSTEM_HAS_TOP_RAIL, 1,""); if (topRailType != null && isTopRailExactMatch) railingType.TopRailType = topRailType.Id; diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertRoof.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertRoof.cs index 3e217070cc..628db56c5b 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertRoof.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertRoof.cs @@ -136,7 +136,7 @@ public ApplicationObject RoofToNative(Roof speckleRoof) for (var i = 0; i < curveArray.Size; i++) revitFootprintRoof.set_DefinesSlope(curveArray.get_Item(i), true); - TrySetParam(revitFootprintRoof, BuiltInParameter.ROOF_SLOPE, (double)speckleFootprintRoof.slope); + TrySetParam(revitFootprintRoof, BuiltInParameter.ROOF_SLOPE, (double)speckleFootprintRoof.slope,""); } if (speckleFootprintRoof.cutOffLevel != null) diff --git a/Objects/Objects/BuiltElements/Revit/Parameter.cs b/Objects/Objects/BuiltElements/Revit/Parameter.cs index 18133943c9..c6f20ba67c 100644 --- a/Objects/Objects/BuiltElements/Revit/Parameter.cs +++ b/Objects/Objects/BuiltElements/Revit/Parameter.cs @@ -14,7 +14,7 @@ public Parameter( [SchemaParamInfo( "(Optional) Speckle units. If not set it's retrieved from the current document. For non lenght based parameters (eg. Air Flow) it should be set to 'none' so that the Revit display unit will be used instead." )] - string units = "" + string units = "" ) { this.name = name; @@ -41,6 +41,6 @@ public Parameter( /// True = Type Parameter, False = Instance Parameter /// public bool isTypeParameter { get; set; } = false; - + public string units { get; set; } } From 461099107a73ee3cdd4288f5449dde0ce1568183 Mon Sep 17 00:00:00 2001 From: timglewis Date: Fri, 18 Aug 2023 00:50:59 +1000 Subject: [PATCH 08/57] Fix: Using System.DoubleNumerics for Transforms (#2800) (#2815) * Added System.DoubleNumerics to the core project Converted Transform.cs to use System.DoubleNumerics Added a new TransformTests.cs file to test the new transform class Changed Converters to use System.DoubleNumerics * Adds handling of new System.DoubleNumerics matrix4x4 to serializer and deserializer * Update ConverterNavisworks.Geometry.cs * updates transform methods in rhino and revit * test(core): Tests matrix serialisation * Added log for backwards compatibility usage --------- Co-authored-by: Claire Kuang Co-authored-by: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> --- Core/Core/Core.csproj | 1 + .../Serialisation/BaseObjectSerializerV2.cs | 67 +++++++---- Core/Core/Serialisation/ValueConverter.cs | 36 +++++- .../Tests/Models/SerializerBreakingChanges.cs | 48 ++++++++ .../Models/SerializerNonBreakingChanges.cs | 83 ++++++++------ .../ConverterAutocadCivil.Geometry.cs | 2 +- .../ConverterNavisworks.Geometry.cs | 8 +- .../Partial Classes/ConvertFamilyInstance.cs | 4 +- .../Partial Classes/ConvertGeometry.cs | 22 +--- .../ConverterRhinoGh.Mappings.cs | 2 +- .../ConverterRhinoGh.Other.cs | 9 +- .../ConverterRhinoGh.cs | 2 +- Objects/Objects/Other/Transform.cs | 104 +++++++++--------- Objects/Tests/Geometry/TransformTests.cs | 2 +- 14 files changed, 250 insertions(+), 140 deletions(-) create mode 100644 Core/Tests/Models/SerializerBreakingChanges.cs diff --git a/Core/Core/Core.csproj b/Core/Core/Core.csproj index ae76e567cf..7ee14fe173 100644 --- a/Core/Core/Core.csproj +++ b/Core/Core/Core.csproj @@ -43,6 +43,7 @@ + diff --git a/Core/Core/Serialisation/BaseObjectSerializerV2.cs b/Core/Core/Serialisation/BaseObjectSerializerV2.cs index dc66ab3b82..cda1bce4b7 100644 --- a/Core/Core/Serialisation/BaseObjectSerializerV2.cs +++ b/Core/Core/Serialisation/BaseObjectSerializerV2.cs @@ -5,11 +5,12 @@ using System.Drawing; using System.Globalization; using System.Linq; -using System.Numerics; +using System.DoubleNumerics; using System.Reflection; using System.Text.RegularExpressions; using System.Threading; using Speckle.Core.Helpers; +using Speckle.Core.Logging; using Speckle.Core.Models; using Speckle.Core.Transports; using Speckle.Newtonsoft.Json; @@ -21,7 +22,7 @@ public class BaseObjectSerializerV2 { private readonly Stopwatch _stopwatch = new(); private bool _busy; - + private List> ParentClosures = new(); private HashSet ParentObjects = new(); @@ -136,26 +137,52 @@ public object PreserializeObject( return c.ToArgb(); if (obj is DateTime t) return t.ToString("o", CultureInfo.InvariantCulture); - if (obj is Matrix4x4 m) - return new List + if (obj is Matrix4x4 md) + return new List + { + md.M11, + md.M12, + md.M13, + md.M14, + md.M21, + md.M22, + md.M23, + md.M24, + md.M31, + md.M32, + md.M33, + md.M34, + md.M41, + md.M42, + md.M43, + md.M44 + }; + if (obj is System.Numerics.Matrix4x4 ms) //BACKWARDS COMPATIBILITY: matrix4x4 changed from System.Numerics float to System.DoubleNumerics double in release 2.16 + { + SpeckleLog.Logger.Warning( + "This kept for backwards compatibility, no one should be using {this}", + "ValueConverter deserialize to System.Numerics.Matrix4x4" + ); + return new List { - m.M11, - m.M12, - m.M13, - m.M14, - m.M21, - m.M22, - m.M23, - m.M24, - m.M31, - m.M32, - m.M33, - m.M34, - m.M41, - m.M42, - m.M43, - m.M44 + ms.M11, + ms.M12, + ms.M13, + ms.M14, + ms.M21, + ms.M22, + ms.M23, + ms.M24, + ms.M31, + ms.M32, + ms.M33, + ms.M34, + ms.M41, + ms.M42, + ms.M43, + ms.M44 }; + } throw new Exception("Unsupported value in serialization: " + type); } diff --git a/Core/Core/Serialisation/ValueConverter.cs b/Core/Core/Serialisation/ValueConverter.cs index f5ff4ddd0b..5b5f02ff7e 100644 --- a/Core/Core/Serialisation/ValueConverter.cs +++ b/Core/Core/Serialisation/ValueConverter.cs @@ -4,7 +4,9 @@ using System.Collections.Generic; using System.Drawing; using System.Globalization; -using System.Numerics; +using Numerics = System.Numerics; +using System.DoubleNumerics; +using Speckle.Core.Logging; namespace Speckle.Core.Serialisation; @@ -197,7 +199,8 @@ public static bool ConvertValue(Type type, object? value, out object? convertedV { if (!isList) return false; - Type arrayElementType = type.GetElementType() ?? throw new ArgumentException("IsArray yet not valid element type", nameof(type)); + Type arrayElementType = + type.GetElementType() ?? throw new ArgumentException("IsArray yet not valid element type", nameof(type)); Array ret = Activator.CreateInstance(type, valueList.Count) as Array; for (int i = 0; i < valueList.Count; i++) @@ -230,9 +233,36 @@ public static bool ConvertValue(Type type, object? value, out object? convertedV return true; } + #region BACKWARDS COMPATIBILITY: matrix4x4 changed from System.Numerics float to System.DoubleNumerics double in release 2.16 + if (type == typeof(Numerics.Matrix4x4) && value is IReadOnlyList lMatrix) + { + SpeckleLog.Logger.Warning("This kept for backwards compatibility, no one should be using {this}", "ValueConverter deserialize to System.Numerics.Matrix4x4"); + float I(int index) => Convert.ToSingle(lMatrix[index]); + convertedValue = new Numerics.Matrix4x4( + I(0), + I(1), + I(2), + I(3), + I(4), + I(5), + I(6), + I(7), + I(8), + I(9), + I(10), + I(11), + I(12), + I(13), + I(14), + I(15) + ); + return true; + } + #endregion + if (type == typeof(Matrix4x4) && value is IReadOnlyList l) { - float I(int index) => Convert.ToSingle(l[index]); + double I(int index) => Convert.ToDouble(l[index]); convertedValue = new Matrix4x4( I(0), I(1), diff --git a/Core/Tests/Models/SerializerBreakingChanges.cs b/Core/Tests/Models/SerializerBreakingChanges.cs new file mode 100644 index 0000000000..893661a923 --- /dev/null +++ b/Core/Tests/Models/SerializerBreakingChanges.cs @@ -0,0 +1,48 @@ +using NUnit.Framework; + +namespace TestsUnit.Models; + +/// +/// Test fixture that documents what property typing changes break backwards/cross/forwards compatibility, and are "breaking" changes. +/// This doesn't guarantee things work this way for SpecklePy +/// Nor does it encompass other tricks (like deserialize callback, or computed json ignored properties) +/// +[ + TestFixture, + Description( + "For certain types, changing property from one type to another is a breaking change, and not backwards/forwards compatible" + ) +] +public class SerializerBreakingChanges : PrimitiveTestFixture +{ + [Test] + public void StringToInt_ShouldThrow() + { + var from = new StringValueMock(); + from.value = "testValue"; + Assert.Throws(() => from.SerializeAsTAndDeserialize()); + } + + [Test, TestCaseSource(nameof(MyEnums))] + public void StringToEnum_ShouldThrow(MyEnum testCase) + { + var from = new StringValueMock { value = testCase.ToString() }; + + Assert.Throws(() => + { + var res = from.SerializeAsTAndDeserialize(); + }); + } + + [ + Test, + Description("Deserialization of a JTokenType.Float to a .NET short/int/long should throw exception"), + TestCaseSource(nameof(Float64TestCases)), + TestCase(1e+30) + ] + public void DoubleToInt_ShouldThrow(double testCase) + { + var from = new DoubleValueMock { value = testCase }; + Assert.Throws(() => from.SerializeAsTAndDeserialize()); + } +} diff --git a/Core/Tests/Models/SerializerNonBreakingChanges.cs b/Core/Tests/Models/SerializerNonBreakingChanges.cs index 3295cb9e05..606f6f16f4 100644 --- a/Core/Tests/Models/SerializerNonBreakingChanges.cs +++ b/Core/Tests/Models/SerializerNonBreakingChanges.cs @@ -1,6 +1,8 @@ +using System.DoubleNumerics; using System.Drawing; using NUnit.Framework; using Speckle.Core.Api; +using Speckle.Core.Helpers; using Speckle.Core.Models; namespace TestsUnit.Models; @@ -115,50 +117,49 @@ public void DoubleToDouble(double testCase) var res = from.SerializeAsTAndDeserialize(); Assert.That(res.value, Is.EqualTo(testCase)); } -} -/// -/// Test fixture that documents what property typing changes break backwards/cross/forwards compatibility, and are "breaking" changes. -/// This doesn't guarantee things work this way for SpecklePy -/// Nor does it encompass other tricks (like deserialize callback, or computed json ignored properties) -/// -[ - TestFixture, - Description( - "For certain types, changing property from one type to another is a breaking change, and not backwards/forwards compatible" - ) -] -public class SerializerBreakingChanges : PrimitiveTestFixture -{ [Test] - public void StringToInt_ShouldThrow() + [TestCase(123, 255)] + [TestCase(256, 1)] + [TestCase(256, float.MinValue)] + public void ListToMatrix64(int seed, double scaler) { - var from = new StringValueMock(); - from.value = "testValue"; - Assert.Throws(() => from.SerializeAsTAndDeserialize()); - } + Random rand = new(seed); + List testCase = Enumerable.Range(0, 16).Select(_ => rand.NextDouble() * scaler).ToList(); - [Test, TestCaseSource(nameof(MyEnums))] - public void StringToEnum_ShouldThrow(MyEnum testCase) - { - var from = new StringValueMock { value = testCase.ToString() }; + ListDoubleValueMock from = new() { value = testCase, }; + + //Test List -> Matrix + var res = from.SerializeAsTAndDeserialize(); + Assert.That(res.value.M11, Is.EqualTo(testCase[0])); + Assert.That(res.value.M44, Is.EqualTo(testCase[^1])); - Assert.Throws(() => - { - var res = from.SerializeAsTAndDeserialize(); - }); + //Test Matrix -> List + var backAgain = res.SerializeAsTAndDeserialize(); + Assert.That(backAgain.value, Is.Not.Null); + Assert.That(backAgain.value, Is.EquivalentTo(testCase)); } - [ - Test, - Description("Deserialization of a JTokenType.Float to a .NET short/int/long should throw exception"), - TestCaseSource(nameof(Float64TestCases)), - TestCase(1e+30) - ] - public void DoubleToInt_ShouldThrow(double testCase) + [Test] + [TestCase(123, 255)] + [TestCase(256, 1)] + [DefaultFloatingPointTolerance(Constants.Eps)] + public void Matrix32ToMatrix64(int seed, float scaler) { - var from = new DoubleValueMock { value = testCase }; - Assert.Throws(() => from.SerializeAsTAndDeserialize()); + Random rand = new(seed); + List testCase = Enumerable.Range(0, 16).Select(_ => rand.NextDouble() * scaler).ToList(); + + ListDoubleValueMock from = new() { value = testCase, }; + + //Test List -> Matrix + var res = from.SerializeAsTAndDeserialize(); + Assert.That(res.value.M11, Is.EqualTo(testCase[0])); + Assert.That(res.value.M44, Is.EqualTo(testCase[^1])); + + //Test Matrix -> List + var backAgain = res.SerializeAsTAndDeserialize(); + Assert.That(backAgain.value, Is.Not.Null); + Assert.That(backAgain.value, Is.EquivalentTo(testCase)); } } @@ -192,6 +193,16 @@ public class DoubleValueMock : SerializerMock public double value { get; set; } } +public class Matrix64ValueMock : SerializerMock +{ + public Matrix4x4 value { get; set; } +} + +public class Matrix32ValueMock : SerializerMock +{ + public System.Numerics.Matrix4x4 value { get; set; } +} + public class ColorValueMock : SerializerMock { public Color value { get; set; } diff --git a/Objects/Converters/ConverterAutocadCivil/ConverterAutocadCivilShared/ConverterAutocadCivil.Geometry.cs b/Objects/Converters/ConverterAutocadCivil/ConverterAutocadCivilShared/ConverterAutocadCivil.Geometry.cs index 440065b716..d940c37572 100644 --- a/Objects/Converters/ConverterAutocadCivil/ConverterAutocadCivilShared/ConverterAutocadCivil.Geometry.cs +++ b/Objects/Converters/ConverterAutocadCivil/ConverterAutocadCivilShared/ConverterAutocadCivil.Geometry.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using System.Numerics; +using System.DoubleNumerics; using System.Linq; using System.Drawing; diff --git a/Objects/Converters/ConverterNavisworks/ConverterNavisworks/ConverterNavisworks.Geometry.cs b/Objects/Converters/ConverterNavisworks/ConverterNavisworks/ConverterNavisworks.Geometry.cs index 5236872ac1..3ec68bed80 100644 --- a/Objects/Converters/ConverterNavisworks/ConverterNavisworks/ConverterNavisworks.Geometry.cs +++ b/Objects/Converters/ConverterNavisworks/ConverterNavisworks/ConverterNavisworks.Geometry.cs @@ -1,8 +1,8 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; -using System.Numerics; +using System.DoubleNumerics; using Autodesk.Navisworks.Api; using Autodesk.Navisworks.Api.Interop.ComApi; using Objects.Geometry; @@ -213,7 +213,7 @@ private static Vector3D ApplyTransformation(Vector3 vector3, IEnumerable private static Vector3 VectorFromVertex(InwSimpleVertex v) { var arrayV = (Array)v.coord; - return new Vector3((float)arrayV.GetValue(1), (float)arrayV.GetValue(2), (float)arrayV.GetValue(3)); + return new Vector3((double)arrayV.GetValue(1), (double)arrayV.GetValue(2), (double)arrayV.GetValue(3)); } } @@ -557,4 +557,4 @@ private static IEnumerable MoveAndScaleVertices(Vector3D vertex1, Vector (vertex1.Z + move.Z) * scale }; } -} \ No newline at end of file +} diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertFamilyInstance.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertFamilyInstance.cs index d5dad4a58c..6d5798531f 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertFamilyInstance.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertFamilyInstance.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Numerics; +using System.DoubleNumerics; using Autodesk.Revit.DB; using Autodesk.Revit.DB.Structure; @@ -456,7 +456,7 @@ private Other.Transform TransformToSpeckle( ); // get the scale: TODO: do revit transforms ever have scaling? - var scale = (float)transform.Scale; + var scale = transform.Scale; return new Other.Transform(vX, vY, vZ, t) { units = ModelUnits }; } diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertGeometry.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertGeometry.cs index 7920d9ecd0..26c2655361 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertGeometry.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertGeometry.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Numerics; +using System.DoubleNumerics; using Autodesk.Revit.DB; using Autodesk.Revit.DB.PointClouds; using Objects.Converters.DxfConverter; @@ -710,22 +710,10 @@ static bool IsNonPlanarQuad(IList points) return false; var matrix = new Matrix4x4( - (float)points[0].X, - (float)points[1].X, - (float)points[2].X, - (float)points[3].X, - (float)points[0].Y, - (float)points[1].Y, - (float)points[2].Y, - (float)points[3].Y, - (float)points[0].Z, - (float)points[1].Z, - (float)points[2].Z, - (float)points[3].Z, - 1, - 1, - 1, - 1 + points[0].X, points[1].X, points[2].X, points[3].X, + points[0].Y, points[1].Y, points[2].Y, points[3].Y, + points[0].Z, points[1].Z, points[2].Z, points[3].Z, + 1, 1, 1, 1 ); return matrix.GetDeterminant() != 0; } diff --git a/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.Mappings.cs b/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.Mappings.cs index 6a2fd75416..62ea17ca4d 100644 --- a/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.Mappings.cs +++ b/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.Mappings.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Numerics; +using System.DoubleNumerics; using Rhino.DocObjects; using RH = Rhino.Geometry; using Speckle.Core.Api; diff --git a/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.Other.cs b/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.Other.cs index 72cf20fc27..b02ec41a73 100644 --- a/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.Other.cs +++ b/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.Other.cs @@ -559,13 +559,18 @@ StringBuilder LayerIdRecurse(TraversalContext context, StringBuilder stringBuild // This results in a transposed transformation matrix - may need to be addressed later public BlockInstance BlockInstanceToSpeckle(RH.InstanceObject instance) { - var t = instance.InstanceXform.ToFloatArray(true); + var t = instance.InstanceXform; + var matrix = new System.DoubleNumerics.Matrix4x4( + t.M00, t.M01, t.M02, t.M03, + t.M10, t.M11, t.M12, t.M13, + t.M20, t.M21, t.M22, t.M23, + t.M30, t.M31, t.M32, t.M33); var def = BlockDefinitionToSpeckle(instance.InstanceDefinition); var _instance = new BlockInstance { - transform = new Other.Transform(t, ModelUnits), + transform = new Other.Transform(matrix, ModelUnits), typedDefinition = def, applicationId = instance.Id.ToString(), units = ModelUnits diff --git a/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.cs b/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.cs index eca5ca61cf..9b66088faf 100644 --- a/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.cs +++ b/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; -using System.Numerics; +using System.DoubleNumerics; using System.Reflection; using Objects.BuiltElements; using Objects.BuiltElements.Revit; diff --git a/Objects/Objects/Other/Transform.cs b/Objects/Objects/Other/Transform.cs index 9f36c0210f..45c6625bc7 100644 --- a/Objects/Objects/Other/Transform.cs +++ b/Objects/Objects/Other/Transform.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using System.Numerics; +using System.DoubleNumerics; using Objects.Geometry; using Speckle.Core.Kits; using Speckle.Core.Logging; @@ -71,18 +71,18 @@ public Transform(Matrix4x4 matrix, string units = null) public Transform(Vector x, Vector y, Vector z, Vector translation) { matrix = new Matrix4x4( - Convert.ToSingle(x.x), - Convert.ToSingle(y.x), - Convert.ToSingle(z.x), - Convert.ToSingle(translation.x), - Convert.ToSingle(x.y), - Convert.ToSingle(y.y), - Convert.ToSingle(z.y), - Convert.ToSingle(translation.y), - Convert.ToSingle(x.z), - Convert.ToSingle(y.z), - Convert.ToSingle(z.z), - Convert.ToSingle(translation.z), + x.x, + y.x, + z.x, + translation.x, + x.y, + y.y, + z.y, + translation.y, + x.z, + y.z, + z.z, + translation.z, 0f, 0f, 0f, @@ -152,10 +152,10 @@ private static Quaternion LookRotation(Vector3 forward, Vector3 up) var num = Math.Sqrt(num8 + 1d); num = 0.5d / num; return new Quaternion( - (float)((m12 - m21) * num), - (float)((m20 - m02) * num), - (float)((m01 - m10) * num), - (float)(num * 0.5d) + (m12 - m21) * num, + (m20 - m02) * num, + (m01 - m10) * num, + num * 0.5d ); } if (m00 >= m11 && m00 >= m22) @@ -163,10 +163,10 @@ private static Quaternion LookRotation(Vector3 forward, Vector3 up) var num7 = Math.Sqrt(1d + m00 - m11 - m22); var num4 = 0.5d / num7; return new Quaternion( - (float)(0.5d * num7), - (float)((m01 + m10) * num4), - (float)((m02 + m20) * num4), - (float)((m12 - m21) * num4) + 0.5d * num7, + (m01 + m10) * num4, + (m02 + m20) * num4, + (m12 - m21) * num4 ); } if (m11 > m22) @@ -174,19 +174,19 @@ private static Quaternion LookRotation(Vector3 forward, Vector3 up) var num6 = Math.Sqrt(1d + m11 - m00 - m22); var num3 = 0.5d / num6; return new Quaternion( - (float)((m10 + m01) * num3), - (float)(0.5d * num6), - (float)((m21 + m12) * num3), - (float)((m20 - m02) * num3) + (m10 + m01) * num3, + 0.5d * num6, + (m21 + m12) * num3, + (m20 - m02) * num3 ); } var num5 = Math.Sqrt(1d + m22 - m00 - m11); var num2 = 0.5d / num5; return new Quaternion( - (float)((m20 + m02) * num2), - (float)((m21 + m12) * num2), - (float)(0.5d * num5), - (float)((m01 - m10) * num2) + (m20 + m02) * num2, + (m21 + m12) * num2, + 0.5d * num5, + (m01 - m10) * num2 ); } @@ -266,29 +266,6 @@ public double[] ToArray() // Creates a matrix4x4 from a double array internal static Matrix4x4 CreateMatrix(double[] value) - { - return new Matrix4x4( - Convert.ToSingle(value[0]), - Convert.ToSingle(value[1]), - Convert.ToSingle(value[2]), - Convert.ToSingle(value[3]), - Convert.ToSingle(value[4]), - Convert.ToSingle(value[5]), - Convert.ToSingle(value[6]), - Convert.ToSingle(value[7]), - Convert.ToSingle(value[8]), - Convert.ToSingle(value[9]), - Convert.ToSingle(value[10]), - Convert.ToSingle(value[11]), - Convert.ToSingle(value[12]), - Convert.ToSingle(value[13]), - Convert.ToSingle(value[14]), - Convert.ToSingle(value[15]) - ); - } - - // Creates a matrix from a float array - internal static Matrix4x4 CreateMatrix(float[] value) { return new Matrix4x4( value[0], @@ -310,6 +287,29 @@ internal static Matrix4x4 CreateMatrix(float[] value) ); } + // Creates a matrix from a float array + internal static Matrix4x4 CreateMatrix(float[] value) + { + return new Matrix4x4( + Convert.ToDouble(value[0]), + Convert.ToDouble(value[1]), + Convert.ToDouble(value[2]), + Convert.ToDouble(value[3]), + Convert.ToDouble(value[4]), + Convert.ToDouble(value[5]), + Convert.ToDouble(value[6]), + Convert.ToDouble(value[7]), + Convert.ToDouble(value[8]), + Convert.ToDouble(value[9]), + Convert.ToDouble(value[10]), + Convert.ToDouble(value[11]), + Convert.ToDouble(value[12]), + Convert.ToDouble(value[13]), + Convert.ToDouble(value[14]), + Convert.ToDouble(value[15]) + ); + } + #region obsolete [JsonIgnore, Obsolete("Use the matrix property")] diff --git a/Objects/Tests/Geometry/TransformTests.cs b/Objects/Tests/Geometry/TransformTests.cs index c6a69ec59f..b9c352e806 100644 --- a/Objects/Tests/Geometry/TransformTests.cs +++ b/Objects/Tests/Geometry/TransformTests.cs @@ -1,5 +1,5 @@ using System.Collections; -using System.Numerics; +using System.DoubleNumerics; using NUnit.Framework; using Objects.Other; using Speckle.Core.Kits; From 596c376a0db4c89ea1cbc960598e60d10ab693dc Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Thu, 17 Aug 2023 15:58:16 +0100 Subject: [PATCH 09/57] fix to log (#2859) --- Core/Core/Serialisation/BaseObjectSerializerV2.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/Core/Serialisation/BaseObjectSerializerV2.cs b/Core/Core/Serialisation/BaseObjectSerializerV2.cs index cda1bce4b7..3858d3597e 100644 --- a/Core/Core/Serialisation/BaseObjectSerializerV2.cs +++ b/Core/Core/Serialisation/BaseObjectSerializerV2.cs @@ -161,7 +161,7 @@ public object PreserializeObject( { SpeckleLog.Logger.Warning( "This kept for backwards compatibility, no one should be using {this}", - "ValueConverter deserialize to System.Numerics.Matrix4x4" + "BaseObjectSerializerV2 serialize System.Numerics.Matrix4x4" ); return new List { From 4ae8cc67d529687b87278a0de2a01491576fed3e Mon Sep 17 00:00:00 2001 From: connorivy <43247197+connorivy@users.noreply.github.com> Date: Thu, 17 Aug 2023 10:11:19 -0500 Subject: [PATCH 10/57] Fix(csi) : more stable section creation (#2857) more stable section creation Co-authored-by: Connor Ivy --- .../Partial Classes/Properties/Convert1DProperty.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Objects/Converters/ConverterCSI/ConverterCSIShared/Partial Classes/Properties/Convert1DProperty.cs b/Objects/Converters/ConverterCSI/ConverterCSIShared/Partial Classes/Properties/Convert1DProperty.cs index 3297c9389b..0d4ce49754 100644 --- a/Objects/Converters/ConverterCSI/ConverterCSIShared/Partial Classes/Properties/Convert1DProperty.cs +++ b/Objects/Converters/ConverterCSI/ConverterCSIShared/Partial Classes/Properties/Convert1DProperty.cs @@ -1,4 +1,4 @@ -using System; +using System; using Objects.Structural.Properties; using Objects.Structural.Properties.Profiles; using System.Linq; @@ -38,10 +38,10 @@ public string Property1DToNative(Property1D property1D, ref ApplicationObject ap var catalogue = new Catalogue(); int? success = null; - if (property1D.profile?.GetType().Equals(catalogue.GetType()) == true) + if (property1D.profile is Catalogue sectionProfile + && !string.IsNullOrEmpty(sectionProfile.catalogueName) + && !string.IsNullOrEmpty(sectionProfile.sectionName)) { - Catalogue sectionProfile = (Catalogue)property1D.profile; - switch (sectionProfile.catalogueName) { case "CA": From 88b538a9492228496bfb2970073c38462e02d0ac Mon Sep 17 00:00:00 2001 From: connorivy <43247197+connorivy@users.noreply.github.com> Date: Fri, 18 Aug 2023 07:40:19 -0500 Subject: [PATCH 11/57] Fix(Revit) : fatal crash in Revit and speed upgrade for large receive operations (#2849) * transactionManager * fix the explosion * transaction manager with benchmarks * get rid of code for timing * fully implement transaction manager * better unhandled exception message * fix unintented change * clean up exception handle --------- Co-authored-by: Connor Ivy Co-authored-by: Alan Rynne --- .../ConnectorRevit/ConnectorRevit.projitems | 1 - ConnectorRevit/ConnectorRevit/Entry/App.cs | 21 ++ .../Storage/StreamStateCache.cs | 1 + .../UI/ConnectorBindingsRevit.Previews.cs | 3 +- .../UI/ConnectorBindingsRevit.Receive.cs | 51 ++-- .../Models}/ErrorEater.cs | 8 +- .../Models/TransactionManager.cs | 237 ++++++++++++++++++ .../RevitSharedResources.projitems | 4 +- .../ConverterRevitShared/ConversionUtils.cs | 10 +- .../ConverterRevitShared/ConverterRevit.cs | 8 +- .../Partial Classes/ConvertView.Schedule.cs | 55 +--- .../Partial Classes/ConvertWall.cs | 16 +- .../ConverterRevitTestsShared/SpeckleUtils.cs | 19 +- 13 files changed, 319 insertions(+), 115 deletions(-) rename ConnectorRevit/{ConnectorRevit/Revit => RevitSharedResources/Models}/ErrorEater.cs (94%) create mode 100644 ConnectorRevit/RevitSharedResources/Models/TransactionManager.cs diff --git a/ConnectorRevit/ConnectorRevit/ConnectorRevit.projitems b/ConnectorRevit/ConnectorRevit/ConnectorRevit.projitems index a54160f280..28433dd394 100644 --- a/ConnectorRevit/ConnectorRevit/ConnectorRevit.projitems +++ b/ConnectorRevit/ConnectorRevit/ConnectorRevit.projitems @@ -42,7 +42,6 @@ - diff --git a/ConnectorRevit/ConnectorRevit/Entry/App.cs b/ConnectorRevit/ConnectorRevit/Entry/App.cs index b5b8cd62ed..733bdc9564 100644 --- a/ConnectorRevit/ConnectorRevit/Entry/App.cs +++ b/ConnectorRevit/ConnectorRevit/Entry/App.cs @@ -101,6 +101,9 @@ private void ControlledApplication_ApplicationInitialized(object sender, Autodes // We need to hook into the AssemblyResolve event before doing anything else // or we'll run into unresolved issues loading dependencies AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(OnAssemblyResolve); + AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; + System.Windows.Forms.Application.ThreadException += Application_ThreadException; + AppInstance = new UIApplication(sender as Application); Setup.Init(ConnectorBindingsRevit.HostAppNameVersion, ConnectorBindingsRevit.HostAppName); @@ -143,6 +146,24 @@ private void ControlledApplication_ApplicationInitialized(object sender, Autodes } } + private void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e) + { + SpeckleLog.Logger.Fatal(e.Exception, "Caught thread exception with message {exceptionMessage}", e.Exception.Message); + } + + private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) + { + if (e.ExceptionObject is Exception ex) + { + SpeckleLog.Logger.Fatal(ex, "Caught unhandled exception. Is terminating : {isTerminating}. Message : {exceptionMessage}", e.IsTerminating, ex.Message); + } + else + { + SpeckleLog.Logger.Fatal("Caught unhandled exception. Is terminating : {isTerminating}. Exception object is of type : {exceptionObjectType}. Exception object to string : {exceptionObjToString}", e.IsTerminating, e.ExceptionObject.GetType(), e.ExceptionObject.ToString()); + } + } + + public Result OnShutdown(UIControlledApplication application) { return Result.Succeeded; diff --git a/ConnectorRevit/ConnectorRevit/Storage/StreamStateCache.cs b/ConnectorRevit/ConnectorRevit/Storage/StreamStateCache.cs index c59760d6a7..204eedfc08 100644 --- a/ConnectorRevit/ConnectorRevit/Storage/StreamStateCache.cs +++ b/ConnectorRevit/ConnectorRevit/Storage/StreamStateCache.cs @@ -38,6 +38,7 @@ public void AddConvertedElements(IConvertedObjectsCache converted { applicationId = @base.applicationId, CreatedIds = elements + .Where(element => element.IsValidObject) .Select(element => element.UniqueId) .ToList(), Converted = elements.Cast().ToList() diff --git a/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Previews.cs b/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Previews.cs index 6d8923c0a7..2a60475626 100644 --- a/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Previews.cs +++ b/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Previews.cs @@ -15,6 +15,7 @@ using Autodesk.Revit.DB.DirectContext3D; using RevitSharedResources.Interfaces; using RevitSharedResources.Models; +using ConnectorRevit.Revit; namespace Speckle.ConnectorRevit.UI { @@ -62,7 +63,7 @@ await APIContext.Run( using (var t = new Transaction(CurrentDoc.Document, $"Baking stream {state.StreamId}")) { t.Start(); - convertedObjects = ConvertReceivedObjects(converter, progress); + convertedObjects = ConvertReceivedObjects(converter, progress, new TransactionManager(null,null)); t.Commit(); } diff --git a/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Receive.cs b/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Receive.cs index 385b14889f..61264bc8d3 100644 --- a/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Receive.cs +++ b/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Receive.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -21,6 +22,7 @@ using Speckle.Core.Logging; using Speckle.Core.Models; using Speckle.Core.Models.GraphTraversal; +using Speckle.Core.Transports; namespace Speckle.ConnectorRevit.UI { @@ -95,57 +97,31 @@ await elementTypeMapper.Map(state.Settings.FirstOrDefault(x => x.Slug == "receiv var (success, exception) = await APIContext.Run(_ => { - string transactionName = $"Baking stream {state.StreamId}"; - using var g = new TransactionGroup(CurrentDoc.Document, transactionName); - using var t = new Transaction(CurrentDoc.Document, transactionName); - - g.Start(); - var failOpts = t.GetFailureHandlingOptions(); - var errorEater = new ErrorEater(converter); - failOpts.SetFailuresPreprocessor(errorEater); - failOpts.SetClearAfterRollback(true); - t.SetFailureHandlingOptions(failOpts); - t.Start(); + using var transactionManager = new TransactionManager(state.StreamId, CurrentDoc.Document); + transactionManager.Start(); try { - converter.SetContextDocument(t); + converter.SetContextDocument(transactionManager); - var convertedObjects = ConvertReceivedObjects(converter, progress); + var convertedObjects = ConvertReceivedObjects(converter, progress, transactionManager); if (state.ReceiveMode == ReceiveMode.Update) DeleteObjects(previousObjects, convertedObjects); previousObjects.AddConvertedElements(convertedObjects); - t.Commit(); - - if (t.GetStatus() == TransactionStatus.RolledBack) - { - var numTotalErrors = errorEater.CommitErrorsDict.Sum(kvp => kvp.Value); - var numUniqueErrors = errorEater.CommitErrorsDict.Keys.Count; - - var exception = errorEater.GetException(); - if (exception == null) - SpeckleLog.Logger.Warning("Revit commit failed with {numUniqueErrors} unique errors and {numTotalErrors} total errors, but the ErrorEater did not capture any exceptions", numUniqueErrors, numTotalErrors); - else - SpeckleLog.Logger.Fatal(exception, "The Revit API could not resolve {numUniqueErrors} unique errors and {numTotalErrors} total errors when trying to commit the Speckle model. The whole transaction is being rolled back.", numUniqueErrors, numTotalErrors); - - return (false, exception ?? new SpeckleException($"The Revit API could not resolve {numUniqueErrors} unique errors and {numTotalErrors} total errors when trying to commit the Speckle model. The whole transaction is being rolled back.")); - } - - g.Assimilate(); + transactionManager.Finish(); return (true, null); } catch (Exception ex) { - SpeckleLog.Logger.Error(ex, "Rolling back connector transaction {transactionName} {transactionType}", transactionName, t.GetType()); + SpeckleLog.Logger.Error(ex, "Rolling back connector transaction"); string message = $"Fatal Error: {ex.Message}"; if (ex is OperationCanceledException) message = "Receive cancelled"; progress.Report.LogOperationError(new Exception($"{message} - Changes have been rolled back", ex)); - t.RollBack(); - g.RollBack(); + transactionManager.RollbackAll(); return (false, ex); //We can't throw exceptions in from RevitTask, but we can return it along with a success status } }).ConfigureAwait(false); @@ -201,7 +177,7 @@ private void DeleteObjects(IReceivedObjectIdMap previousObjects, } } - private IConvertedObjectsCache ConvertReceivedObjects(ISpeckleConverter converter, ProgressViewModel progress) + private IConvertedObjectsCache ConvertReceivedObjects(ISpeckleConverter converter, ProgressViewModel progress, TransactionManager transactionManager) { using var _d0 = LogContext.PushProperty("converterName", converter.Name); using var _d1 = LogContext.PushProperty("converterAuthor", converter.Author); @@ -240,8 +216,13 @@ private IConvertedObjectsCache ConvertReceivedObjects(ISpeckleCon if (!receiveLinkedModels && @base["isRevitLinkedModel"] != null && bool.Parse(@base["isRevitLinkedModel"].ToString())) continue; + transactionManager.StartSubtransaction(); var convRes = converter.ConvertToNative(@base); + transactionManager.CommitSubtransaction(); + RefreshView(); + if (index % 50 == 0) + transactionManager.Commit(); switch (convRes) { @@ -255,6 +236,7 @@ private IConvertedObjectsCache ConvertReceivedObjects(ISpeckleCon } catch (ConversionNotReadyException ex) { + transactionManager.RollbackSubTransaction(); var notReadyDataCache = revitDocumentAggregateCache .GetOrInitializeEmptyCacheOfType(out _); var notReadyData = notReadyDataCache @@ -275,6 +257,7 @@ private IConvertedObjectsCache ConvertReceivedObjects(ISpeckleCon } catch (Exception ex) { + transactionManager.RollbackSubTransaction(); SpeckleLog.Logger.Warning(ex, "Failed to convert"); obj.Update(status: ApplicationObject.State.Failed, logItem: ex.Message); progress.Report.UpdateReportObject(obj); diff --git a/ConnectorRevit/ConnectorRevit/Revit/ErrorEater.cs b/ConnectorRevit/RevitSharedResources/Models/ErrorEater.cs similarity index 94% rename from ConnectorRevit/ConnectorRevit/Revit/ErrorEater.cs rename to ConnectorRevit/RevitSharedResources/Models/ErrorEater.cs index 679be91ea6..5475e60232 100644 --- a/ConnectorRevit/ConnectorRevit/Revit/ErrorEater.cs +++ b/ConnectorRevit/RevitSharedResources/Models/ErrorEater.cs @@ -5,19 +5,13 @@ using Speckle.Core.Kits; using Speckle.Core.Logging; -namespace ConnectorRevit.Revit +namespace RevitSharedResources.Models { public class ErrorEater : IFailuresPreprocessor { - private ISpeckleConverter _converter; private List _exceptions = new(); public Dictionary CommitErrorsDict = new(); - public ErrorEater(ISpeckleConverter converter) - { - _converter = converter; - } - public FailureProcessingResult PreprocessFailures(FailuresAccessor failuresAccessor) { var resolvedFailures = 0; diff --git a/ConnectorRevit/RevitSharedResources/Models/TransactionManager.cs b/ConnectorRevit/RevitSharedResources/Models/TransactionManager.cs new file mode 100644 index 0000000000..0ce3950a8a --- /dev/null +++ b/ConnectorRevit/RevitSharedResources/Models/TransactionManager.cs @@ -0,0 +1,237 @@ +using System; +using System.Linq; +using Autodesk.Revit.DB; +using RevitSharedResources.Interfaces; +using Speckle.Core.Logging; + +namespace RevitSharedResources.Models +{ + /// + /// Is responsible for all functionality regarding subtransactions, transactions, and transaction groups. + /// This includes starting, pausing, committing, and rolling back transactions + /// + public class TransactionManager : IDisposable + { + private string streamId; + private Document document; + public TransactionManager(string streamId, Document document) + { + this.streamId = streamId; + this.document = document; + } + + private ErrorEater errorEater; + private bool isDisposed; + private TransactionGroup transactionGroup; + private Transaction transaction; + private SubTransaction subTransaction; + + public void Finish() + { + try + { + Commit(); + } + finally + { + if (transactionGroup.GetStatus() == TransactionStatus.Started) + { + transactionGroup.Assimilate(); + } + transactionGroup?.Dispose(); + } + } + + public void Start() + { + if (transactionGroup == null) + { + transactionGroup = new TransactionGroup(document, $"Baking stream {streamId}"); + transactionGroup.Start(); + } + + if (transaction == null + || !transaction.IsValidObject + || transaction.GetStatus() != TransactionStatus.Started) + { + transaction = new Transaction(document, $"Baking stream {streamId}"); + var failOpts = transaction.GetFailureHandlingOptions(); + errorEater = new ErrorEater(); + failOpts.SetFailuresPreprocessor(errorEater); + failOpts.SetClearAfterRollback(true); + transaction.SetFailureHandlingOptions(failOpts); + transaction.Start(); + } + } + + public TransactionStatus Commit() + { + if (subTransaction != null + && subTransaction.IsValidObject + && subTransaction.GetStatus() == TransactionStatus.Started) + { + HandleFailedCommit(subTransaction.Commit()); + subTransaction.Dispose(); + } + if (transaction != null + && transaction.IsValidObject + && transaction.GetStatus() == TransactionStatus.Started) + { + var status = transaction.Commit(); + HandleFailedCommit(status); + transaction.Dispose(); + return status; + } + return TransactionStatus.Uninitialized; + } + + private void HandleFailedCommit(TransactionStatus status) + { + if (status == TransactionStatus.RolledBack) + { + var numTotalErrors = errorEater.CommitErrorsDict.Sum(kvp => kvp.Value); + var numUniqueErrors = errorEater.CommitErrorsDict.Keys.Count; + + var exception = errorEater.GetException(); + if (exception == null) + SpeckleLog.Logger.Fatal("Revit commit failed with {numUniqueErrors} unique errors and {numTotalErrors} total errors, but the ErrorEater did not capture any exceptions", numUniqueErrors, numTotalErrors); + else + SpeckleLog.Logger.Fatal(exception, "The Revit API could not resolve {numUniqueErrors} unique errors and {numTotalErrors} total errors when trying to commit the Speckle model. The whole transaction is being rolled back.", numUniqueErrors, numTotalErrors); + + throw exception ?? new SpeckleException($"The Revit API could not resolve {numUniqueErrors} unique errors and {numTotalErrors} total errors when trying to commit the Speckle model. The whole transaction is being rolled back."); + } + } + + public void RollbackTransaction() + { + RollbackSubTransaction(); + if (transaction != null + && transaction.IsValidObject + && transaction.GetStatus() == TransactionStatus.Started) + { + transaction.RollBack(); + } + } + + public void RollbackSubTransaction() + { + if (subTransaction != null + && subTransaction.IsValidObject + && subTransaction.GetStatus() == TransactionStatus.Started) + { + subTransaction.RollBack(); + } + } + + public void RollbackAll() + { + RollbackTransaction(); + if (transactionGroup != null + && transactionGroup.IsValidObject + && transactionGroup.GetStatus() == TransactionStatus.Started) + { + transactionGroup.Assimilate(); + } + } + + public void StartSubtransaction() + { + Start(); + if (subTransaction == null + || !subTransaction.IsValidObject + || subTransaction.GetStatus() != TransactionStatus.Started) + { + subTransaction = new SubTransaction(document); + subTransaction.Start(); + } + } + + public TransactionStatus CommitSubtransaction() + { + if (subTransaction != null && subTransaction.IsValidObject) + { + var status = subTransaction.Commit(); + HandleFailedCommit(status); + subTransaction.Dispose(); + return status; + } + return TransactionStatus.Uninitialized; + } + + public TResult ExecuteInTemporaryTransaction(Func function) + { + return ExecuteInTemporaryTransaction(function, document); + } + public static TResult ExecuteInTemporaryTransaction(Func function, Document document) + { + TResult result = default; + if (!document.IsModifiable) + { + using var t = new Transaction(document, "This Transaction Will Never Get Committed"); + try + { + t.Start(); + result = function(); + } + catch + { + // ignore because we're just going to rollback + } + finally + { + t.RollBack(); + } + } + else + { + using var t = new SubTransaction(document); + try + { + t.Start(); + result = function(); + } + catch + { + // ignore because we're just going to rollback + } + finally + { + t.RollBack(); + } + } + + return result; + } + + #region disposal + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (isDisposed) return; + + if (disposing) + { + // free managed resources + if (subTransaction != null && subTransaction.IsValidObject) + subTransaction.Dispose(); + if (transaction != null && transaction.IsValidObject) + transaction.Dispose(); + if (transactionGroup != null && transactionGroup.IsValidObject) + transactionGroup.Dispose(); + } + + isDisposed = true; + } + + ~TransactionManager() + { + Dispose(false); + } + #endregion + } +} diff --git a/ConnectorRevit/RevitSharedResources/RevitSharedResources.projitems b/ConnectorRevit/RevitSharedResources/RevitSharedResources.projitems index 37e74676a9..70a9518b95 100644 --- a/ConnectorRevit/RevitSharedResources/RevitSharedResources.projitems +++ b/ConnectorRevit/RevitSharedResources/RevitSharedResources.projitems @@ -1,4 +1,4 @@ - + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) @@ -26,6 +26,8 @@ + + diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/ConversionUtils.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/ConversionUtils.cs index 6ade95f84e..cc13cf19e9 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/ConversionUtils.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/ConversionUtils.cs @@ -13,6 +13,7 @@ using RevitSharedResources.Interfaces; using Speckle.Core.Helpers; using Speckle.Core.Kits; +using Speckle.Core.Logging; using Speckle.Core.Models; using Speckle.Core.Models.Extensions; using Speckle.Core.Models.GraphTraversal; @@ -141,6 +142,7 @@ out List notes } catch (Exception ex) { + SpeckleLog.Logger.Error(ex, ex.Message); reportObj.Update(status: ApplicationObject.State.Failed, logItem: $"Conversion threw exception: {ex}"); } } @@ -216,15 +218,19 @@ public ApplicationObject SetHostedElements(Base @base, Element host, Application try { + transactionManager.StartSubtransaction(); var res = ConvertToNative(obj); + transactionManager.CommitSubtransaction(); if (res is ApplicationObject apl) appObj.Update(createdIds: apl.CreatedIds, converted: apl.Converted); } - catch (Exception e) + catch (Exception ex) { appObj.Update( - logItem: $"Failed to create hosted element {obj.speckle_type} in host ({host.Id}): \n{e.Message}" + logItem: $"Failed to create hosted element {obj.speckle_type} in host ({host.Id}): \n{ex.Message}" ); + SpeckleLog.Logger.Error(ex, ex.Message); + transactionManager.RollbackSubTransaction(); continue; } CurrentHostElement = host; // set this again in case this is a deeply hosted element diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/ConverterRevit.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/ConverterRevit.cs index 5dbcf7cc5b..0f0bd5320d 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/ConverterRevit.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/ConverterRevit.cs @@ -8,6 +8,7 @@ using RevitSharedResources.Helpers; using RevitSharedResources.Helpers.Extensions; using RevitSharedResources.Interfaces; +using RevitSharedResources.Models; using Speckle.Core.Kits; using Speckle.Core.Models; using BE = Objects.BuiltElements; @@ -75,8 +76,6 @@ public partial class ConverterRevit : ISpeckleConverter public ProgressReport Report { get; private set; } = new ProgressReport(); - public Transaction T { get; private set; } - public Dictionary Settings { get; private set; } = new Dictionary(); public Dictionary Levels { get; private set; } = new Dictionary(); @@ -108,12 +107,13 @@ public ConverterRevit() private IRevitDocumentAggregateCache revitDocumentAggregateCache; private IConvertedObjectsCache receivedObjectsCache; + private TransactionManager transactionManager; public void SetContextDocument(object doc) { - if (doc is Transaction t) + if (doc is TransactionManager transactionManager) { - T = t; + this.transactionManager = transactionManager; } else if (doc is IRevitDocumentAggregateCache revitDocumentAggregateCache) { diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertView.Schedule.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertView.Schedule.cs index b21fb11bfe..659f20a6ce 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertView.Schedule.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertView.Schedule.cs @@ -4,6 +4,7 @@ using System.Linq; using Autodesk.Revit.DB; using Objects.Organization; +using RevitSharedResources.Models; using Speckle.Core.Logging; using Speckle.Core.Models; using DB = Autodesk.Revit.DB; @@ -281,11 +282,11 @@ private int[] GetTableHeaderIndexArray(ViewSchedule revitSchedule, List { if (!revitSchedule.Definition.ShowHeaders) { - return RevitScheduleUtils.ExecuteInTemporaryTransaction(() => + return TransactionManager.ExecuteInTemporaryTransaction(() => { revitSchedule.Definition.ShowHeaders = true; return GetHeaderIndexArrayFromScheduleWithHeaders(revitSchedule, columnHeaders); - }, Doc); + }, revitSchedule.Document); } return GetHeaderIndexArrayFromScheduleWithHeaders(revitSchedule, columnHeaders); @@ -362,9 +363,14 @@ private static List GetRowValues(ViewSchedule revitSchedule, SectionType } #endregion - public static IEnumerable ElementApplicationIdsInRow(int rowNumber, TableSectionData section, ICollection orginialTableIds, DB.ViewSchedule revitSchedule, SectionType tableSection) + public static IEnumerable ElementApplicationIdsInRow( + int rowNumber, + TableSectionData section, + ICollection orginialTableIds, + DB.ViewSchedule revitSchedule, + SectionType tableSection) { - var remainingIdsInRow = RevitScheduleUtils.ExecuteInTemporaryTransaction(() => + var remainingIdsInRow = TransactionManager.ExecuteInTemporaryTransaction(() => { section.RemoveRow(rowNumber); return new FilteredElementCollector(revitSchedule.Document, revitSchedule.Id) @@ -478,46 +484,5 @@ public static IEnumerable ScheduleColumnIteration(V }; } } - - public static TResult ExecuteInTemporaryTransaction(Func function, Document doc) - { - TResult result = default; - if (!doc.IsModifiable) - { - using var t = new Transaction(doc, "This Transaction Will Never Get Committed"); - try - { - t.Start(); - result = function(); - } - catch - { - // ignore because we're just going to rollback - } - finally - { - t.RollBack(); - } - } - else - { - using var t = new SubTransaction(doc); - try - { - t.Start(); - result = function(); - } - catch - { - // ignore because we're just going to rollback - } - finally - { - t.RollBack(); - } - } - - return result; - } } } diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertWall.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertWall.cs index f5d82c6f43..35b08f9d8c 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertWall.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertWall.cs @@ -284,10 +284,10 @@ private void SetWallVoids(Wall wall, Base speckleElement) Doc.Regenerate(); var sketch = (Sketch)Doc.GetElement(wall.SketchId); - T.Commit(); + transactionManager.Commit(); var sketchEditScope = new SketchEditScope(Doc, "Add profile to the sketch"); sketchEditScope.Start(sketch.Id); - T.Start(); + transactionManager.StartSubtransaction(); foreach (var obj in voidCurves) { @@ -298,7 +298,7 @@ private void SetWallVoids(Wall wall, Base speckleElement) var curveArray = CurveToNative(@void, true); Doc.Create.NewModelCurveArray(curveArray, sketch.SketchPlane); } - if (T.Commit() != TransactionStatus.Committed) + if (transactionManager.Commit() != TransactionStatus.Committed) sketchEditScope.Cancel(); try @@ -307,9 +307,15 @@ private void SetWallVoids(Wall wall, Base speckleElement) } catch (Exception ex) { - sketchEditScope.Cancel(); + if (sketchEditScope.IsActive) + { + sketchEditScope.Cancel(); + } + } + finally + { + transactionManager.StartSubtransaction(); } - T.Start(); #endif } diff --git a/Objects/Converters/ConverterRevit/ConverterRevitTests/ConverterRevitTestsShared/SpeckleUtils.cs b/Objects/Converters/ConverterRevit/ConverterRevitTests/ConverterRevitTestsShared/SpeckleUtils.cs index b7a9707028..78c7fe2e18 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitTests/ConverterRevitTestsShared/SpeckleUtils.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitTests/ConverterRevitTestsShared/SpeckleUtils.cs @@ -22,29 +22,18 @@ internal async static Task RunInTransaction(Action action, DB.Document d await APIContext.Run(() => { - using var g = new DB.TransactionGroup(doc, transactionName); - using var transaction = new DB.Transaction(doc, transactionName); - - g.Start(); - transaction.Start(); + using var transactionManager = new TransactionManager("", doc); + transactionManager.Start(); if (converter != null) { - converter.SetContextDocument(transaction); - } - - if (ignoreWarnings) - { - var options = transaction.GetFailureHandlingOptions(); - options.SetFailuresPreprocessor(new IgnoreAllWarnings()); - transaction.SetFailureHandlingOptions(options); + converter.SetContextDocument(transactionManager); } try { action.Invoke(); - transaction.Commit(); - g.Assimilate(); + transactionManager.Finish(); } catch (Exception exception) { From 272ee3cdb9807053309bff0fb3d31428d297a2d2 Mon Sep 17 00:00:00 2001 From: Alan Rynne Date: Mon, 21 Aug 2023 11:16:04 +0200 Subject: [PATCH 12/57] fix(rvt): Send objects that have no 3d representation to Speckle (#2856) feat(rvt): Send elements without display meshes --- .../Partial Classes/ConvertRevitElement.cs | 44 +++++++++++++------ 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertRevitElement.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertRevitElement.cs index 59f2ac7352..66553af79a 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertRevitElement.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertRevitElement.cs @@ -37,18 +37,19 @@ public RevitElement RevitElementToSpeckle(Element revitElement, out List speckleElement.category = revitElement.Category.Name; GetHostedElements(speckleElement, revitElement, out notes); - - var elements = (speckleElement["elements"] ?? @speckleElement["@elements"]) as List; - elements ??= new List(); - // get the displayvalue of this revit element - var displayValue = GetElementDisplayValue(revitElement, new Options() { DetailLevel = ViewDetailLevel.Fine }); - if (!displayValue.Any() && elements.Count == 0) - { - notes.Add("Not sending elements without display meshes"); - return null; - } - speckleElement.displayValue = displayValue; + // get the displayValue of this revit element + using var options = new Options(); + options.DetailLevel = ViewDetailLevel.Fine; + + var displayValue = GetElementDisplayValue(revitElement, options); + + if (!displayValue.Any()) + notes.Add( + "Element does not have visible geometry. It will be sent to Speckle but won't be visible in the viewer." + ); + else + speckleElement.displayValue = displayValue; GetAllRevitParamsAndIds(speckleElement, revitElement); @@ -65,17 +66,32 @@ public RevitElementType ElementTypeToSpeckle(ElementType revitType) switch (revitType) { case FamilySymbol o: - var symbolType = new RevitSymbolElementType() { type = type, family = family, category = category }; + var symbolType = new RevitSymbolElementType() + { + type = type, + family = family, + category = category + }; symbolType.placementType = o.Family?.FamilyPlacementType.ToString(); speckleType = symbolType; break; case MEPCurveType o: - var mepType = new RevitMepElementType() { type = type, family = family, category = category }; + var mepType = new RevitMepElementType() + { + type = type, + family = family, + category = category + }; mepType.shape = o.Shape.ToString(); speckleType = mepType; break; default: - speckleType = new RevitElementType() { type = type, family = family, category = category }; + speckleType = new RevitElementType() + { + type = type, + family = family, + category = category + }; break; } From 2433c688dae5f44919a47b8de47b275ed74c289e Mon Sep 17 00:00:00 2001 From: Claire Kuang Date: Tue, 22 Aug 2023 12:04:31 +0100 Subject: [PATCH 13/57] feat(rhino): adds ceiling and footprint roof mappings (#2866) * adds ceiling and roof mappings * renamed footprint roofs --- .../UI/MappingBindingsRhino.cs | 4 ++ .../MappingTool/MappingsViewModel.cs | 28 +++++++++ .../ViewModels/MappingTool/Schemas.cs | 62 +++++++++++++++++++ .../DesktopUI2/Views/MappingsControl.xaml | 10 +++ .../ConverterRhinoGh.Mappings.cs | 24 +++++-- 5 files changed, 124 insertions(+), 4 deletions(-) diff --git a/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/MappingBindingsRhino.cs b/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/MappingBindingsRhino.cs index bd95b91b05..7f032e2d07 100644 --- a/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/MappingBindingsRhino.cs +++ b/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/MappingBindingsRhino.cs @@ -201,6 +201,10 @@ bool HasPlanarBottomEdge(Brep brp) { schemas.Add(new RevitFloorViewModel()); schemas.Add(new RevitDefaultFloorViewModel()); + schemas.Add(new RevitCeilingViewModel()); + schemas.Add(new RevitDefaultCeilingViewModel()); + schemas.Add(new RevitFootprintRoofViewModel()); + schemas.Add(new RevitDefaultRoofViewModel()); } else if (isVertical) { diff --git a/DesktopUI2/DesktopUI2/ViewModels/MappingTool/MappingsViewModel.cs b/DesktopUI2/DesktopUI2/ViewModels/MappingTool/MappingsViewModel.cs index 9b0c601f16..60fc80505c 100644 --- a/DesktopUI2/DesktopUI2/ViewModels/MappingTool/MappingsViewModel.cs +++ b/DesktopUI2/DesktopUI2/ViewModels/MappingTool/MappingsViewModel.cs @@ -318,6 +318,8 @@ schema is DirectShapeFreeformViewModel || schema is RevitTopographyViewModel || schema is RevitDefaultWallViewModel || schema is RevitDefaultFloorViewModel + || schema is RevitDefaultCeilingViewModel + || schema is RevitDefaultRoofViewModel || schema is RevitDefaultBeamViewModel || schema is RevitDefaultBraceViewModel || schema is RevitDefaultColumnViewModel @@ -381,6 +383,32 @@ schema is DirectShapeFreeformViewModel updatedSchemas.Add(o); break; + case RevitCeilingViewModel o: + var ceilingFamilies = AvailableRevitTypes.Where(x => x.category == "Ceilings").ToList(); + if (!ceilingFamilies.Any() || !AvailableRevitLevels.Any()) + break; + var ceilingFamiliesViewModels = ceilingFamilies + .GroupBy(x => x.family) + .Select(g => new RevitFamily(g.Key.ToString(), g.Select(y => y.type).ToList())) + .ToList(); + o.Families = ceilingFamiliesViewModels; + o.Levels = AvailableRevitLevels; + updatedSchemas.Add(o); + break; + + case RevitFootprintRoofViewModel o: + var roofFamilies = AvailableRevitTypes.Where(x => x.category == "Roofs").ToList(); + if (!roofFamilies.Any() || !AvailableRevitLevels.Any()) + break; + var roofFamiliesViewModels = roofFamilies + .GroupBy(x => x.family) + .Select(g => new RevitFamily(g.Key.ToString(), g.Select(y => y.type).ToList())) + .ToList(); + o.Families = roofFamiliesViewModels; + o.Levels = AvailableRevitLevels; + updatedSchemas.Add(o); + break; + case RevitBeamViewModel o: var beamFamilies = AvailableRevitTypes.Where(x => x.category == "Structural Framing").ToList(); if (!beamFamilies.Any() || !AvailableRevitLevels.Any()) diff --git a/DesktopUI2/DesktopUI2/ViewModels/MappingTool/Schemas.cs b/DesktopUI2/DesktopUI2/ViewModels/MappingTool/Schemas.cs index db04d94cc9..92bfcdfbb4 100644 --- a/DesktopUI2/DesktopUI2/ViewModels/MappingTool/Schemas.cs +++ b/DesktopUI2/DesktopUI2/ViewModels/MappingTool/Schemas.cs @@ -4,6 +4,7 @@ using System.Runtime.Serialization; using Objects.BuiltElements; using Objects.BuiltElements.Revit; +using Objects.BuiltElements.Revit.RevitRoof; using ReactiveUI; using Speckle.Core.Api; using Speckle.Newtonsoft.Json; @@ -238,6 +239,37 @@ public override string GetSerializedSchema() } } +public class RevitCeilingViewModel : RevitBasicViewModel +{ + public override string Name => "Ceiling"; + + public override string GetSerializedSchema() + { + var obj = new RevitCeiling( + null, + SelectedFamily.Name, + SelectedType, + new RevitLevel(SelectedLevel), + 0, + null, + null, + null + ); + return Operations.Serialize(obj); + } +} + +public class RevitFootprintRoofViewModel : RevitBasicViewModel +{ + public override string Name => "FootprintRoof"; + + public override string GetSerializedSchema() + { + var obj = new RevitFootprintRoof(null, SelectedFamily.Name, SelectedType, new RevitLevel(SelectedLevel)); + return Operations.Serialize(obj); + } +} + public class RevitBeamViewModel : RevitBasicViewModel { public override string Name => "Beam"; @@ -435,6 +467,36 @@ public override string GetSerializedSchema() } } +public class RevitDefaultCeilingViewModel : Schema +{ + public override string Name => "Default Ceiling"; + + public override string Summary => Name; + + public override bool IsValid => true; + + public override string GetSerializedSchema() + { + var obj = new Ceiling(); + return Operations.Serialize(obj); + } +} + +public class RevitDefaultRoofViewModel : Schema +{ + public override string Name => "Default Roof"; + + public override string Summary => Name; + + public override bool IsValid => true; + + public override string GetSerializedSchema() + { + var obj = new Roof(); + return Operations.Serialize(obj); + } +} + public class RevitDefaultBeamViewModel : Schema { public override string Name => "Default Beam"; diff --git a/DesktopUI2/DesktopUI2/Views/MappingsControl.xaml b/DesktopUI2/DesktopUI2/Views/MappingsControl.xaml index 7ab164e22b..d160f47e97 100644 --- a/DesktopUI2/DesktopUI2/Views/MappingsControl.xaml +++ b/DesktopUI2/DesktopUI2/Views/MappingsControl.xaml @@ -266,6 +266,8 @@ + + @@ -288,6 +290,14 @@ + + + + + + + + diff --git a/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.Mappings.cs b/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.Mappings.cs index 62ea17ca4d..e18a5651ee 100644 --- a/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.Mappings.cs +++ b/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.Mappings.cs @@ -84,11 +84,27 @@ private Base MappingToSpeckle(string mapping, RhinoObject @object, List break; case Floor o: + var floorBrep = (RH.Brep)@object.Geometry; + var extFloorCurves = GetSurfaceBrepEdges(floorBrep); // extract outline + var intFloorCurves = GetSurfaceBrepEdges(floorBrep, getInterior: true); // extract voids + o.outline = extFloorCurves.First(); + o.voids = intFloorCurves; + break; + + case Ceiling o: + var ceilingBrep = (RH.Brep)@object.Geometry; + var extCeilingCurves = GetSurfaceBrepEdges(ceilingBrep); // extract outline + var intCeilingCurves = GetSurfaceBrepEdges(ceilingBrep, getInterior: true); // extract voids + o.outline = extCeilingCurves.First(); + o.voids = intCeilingCurves; + break; + + case Roof o: var brep = (RH.Brep)@object.Geometry; - var extCurves = GetSurfaceBrepEdges(brep); // extract outline - var intCurves = GetSurfaceBrepEdges(brep, getInterior: true); // extract voids - o.outline = extCurves.First(); - o.voids = intCurves; + var extRoofCurves = GetSurfaceBrepEdges(brep); // extract outline + var intRoofCurves = GetSurfaceBrepEdges(brep, getInterior: true); // extract voids + o.outline = extRoofCurves.First(); + o.voids = intRoofCurves; break; case Beam o: From dfdc8c3fe42ea52cd91a9bd6b7d912a02aafb761 Mon Sep 17 00:00:00 2001 From: Claire Kuang Date: Wed, 23 Aug 2023 09:16:18 +0100 Subject: [PATCH 14/57] feat(revit): adds fallback option to receive displayable speckle objects as directshapes (#2841) * feat(rvt): Adds dynamic displayableObject conversion support This aligns the logic with the viewer's selection logic, where any displayable object will be selectable as a unit, independently of how many geometries it's made of. * fix(rvt): DirectShape would fail if appId was null * fix(rvt): Minor fixes and refactor in DisplayableObject conversion * hack(rvt): Temporary modification of DirectMesh receive logic * modifies traversal and adds secondary conversion routine for failed conversion * Update ConnectorBindingsRevit.Receive.cs * adds setting modification to retry conversion * refactored settings * Update ConnectorBindingsRevit.Receive.cs * adds displayable check for failed conversion * Update ConnectorBindingsRevit.Receive.cs * Update ConnectorBindingsRevit.Previews.cs * refactored to handle nulls * Update ConnectorBindingsRevit.Receive.cs * Update ConnectorBindingsRevit.Receive.cs * temp: Added instance logic in DisplayableObjectToNative * Update ConnectorBindingsRevit.Receive.cs * Update ConnectorBindingsRevit.Receive.cs * Update ConnectorBindingsRevit.Receive.cs * fix(objects): Missing renderMaterial for meshes transformTo * Update ConnectorBindingsRevit.Receive.cs * fix(core): Adjust TryGetDisplayValue's incorrect assumptions * Update ConnectorBindingsRevit.Receive.cs * changes traversal back to original revit traversal and adds current host element setting * adds recursive conversion logic for nested elements * set current host in converter * Update ConvertWall.cs * Update ConnectorBindingsRevit.Receive.cs * fixes nesting logic * deprecates sethostedelements in converter * fixed host bugs * Update ConvertDirectShape.cs * Update ConnectorBindingsRevit.Receive.cs * Update ConverterRevit.cs * refactors logic to add new converter bindings * partial(rvt): DirectShape fallback working with new setting + resolution of some conflicting changes after dev merge (#2861) * fix(core): Removed duplicated methods on `ISpeckleConverter` after non-conflicting merge * fix(rvt): Incorrectly merged chunks caused failure to build * feat(rvt): Isolated `HostElement` in new `RevitConverterState` * feat(rvt): Added directshape fallback setting * fix(rvt): Reorganized fallback to DS behaviour * feat(rvt): Added new setting for DirectShape fallback Still pending backwards compatibility with the previous setting, seems to work as intended right now * fix(rvt): Minor cleanup of fallback logic * fix(rvt): Update appObject when everything fails * fix(rvt): Added transactionManager as input of ConvertObject * fix(rvt): Missed one ConvertObject ref * fix(rvt): Prevent Connector2022 from copying files if not on windows * small transactionManager tweak * fix(rhino): Replace First() call with FirstOrDefault() --------- Co-authored-by: Alan Rynne Co-authored-by: Connor Ivy Co-authored-by: Alan Rynne --- ConnectorRevit/ConnectorRevit/Entry/App.cs | 78 +++- .../Storage/StreamStateCache.cs | 18 +- .../UI/ConnectorBindingsRevit .Settings.cs | 86 ++++- .../UI/ConnectorBindingsRevit.Previews.cs | 31 +- .../UI/ConnectorBindingsRevit.Receive.cs | 362 +++++++++++++----- .../UI/ConnectorBindingsRevit.Send.cs | 116 +++--- .../ConnectorRevit2022.csproj | 2 +- .../Models/RevitConverterState.cs | 10 + .../Models/TransactionManager.cs | 69 ++-- .../RevitSharedResources.projitems | 3 +- .../UI/ConnectorBindingsRhino.Receive.cs | 2 +- Core/Core/Models/Extensions.cs | 46 +++ .../Models/GraphTraversal/GraphTraversal.cs | 15 +- .../ConverterRevitShared/ConversionUtils.cs | 106 ++--- .../ConverterRevitShared/ConverterRevit.cs | 50 ++- .../ConverterRevitShared.projitems | 1 + .../Partial Classes/ConvertBeam.cs | 2 +- .../Partial Classes/ConvertCeiling.cs | 2 +- .../Partial Classes/ConvertColumn.cs | 2 +- .../Partial Classes/ConvertDirectShape.cs | 24 +- .../ConvertDisplayableObject.cs | 91 +++++ .../Partial Classes/ConvertFaceWall.cs | 4 +- .../Partial Classes/ConvertFamilyInstance.cs | 2 +- .../Partial Classes/ConvertFloor.cs | 2 +- .../Partial Classes/ConvertProfileWall.cs | 2 +- .../Partial Classes/ConvertRoof.cs | 2 +- .../Partial Classes/ConvertView.Schedule.cs | 193 +++++++--- .../Partial Classes/ConvertWall.cs | 63 +-- .../ConverterRevitTestsShared/SpeckleUtils.cs | 33 +- Objects/Objects/Geometry/Mesh.cs | 11 +- 30 files changed, 988 insertions(+), 440 deletions(-) create mode 100644 ConnectorRevit/RevitSharedResources/Models/RevitConverterState.cs create mode 100644 Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertDisplayableObject.cs diff --git a/ConnectorRevit/ConnectorRevit/Entry/App.cs b/ConnectorRevit/ConnectorRevit/Entry/App.cs index 733bdc9564..20ceb40060 100644 --- a/ConnectorRevit/ConnectorRevit/Entry/App.cs +++ b/ConnectorRevit/ConnectorRevit/Entry/App.cs @@ -15,7 +15,6 @@ namespace Speckle.ConnectorRevit.Entry { public class App : IExternalApplication { - public static UIApplication AppInstance { get; set; } public static UIControlledApplication UICtrlApp { get; set; } @@ -40,7 +39,15 @@ public Result OnStartup(UIControlledApplication application) string path = typeof(App).Assembly.Location; //desktopui 2 - var speckleButton2 = specklePanel.AddItem(new PushButtonData("Speckle 2", "Revit Connector", typeof(App).Assembly.Location, typeof(SpeckleRevitCommand).FullName)) as PushButton; + var speckleButton2 = + specklePanel.AddItem( + new PushButtonData( + "Speckle 2", + "Revit Connector", + typeof(App).Assembly.Location, + typeof(SpeckleRevitCommand).FullName + ) + ) as PushButton; if (speckleButton2 != null) { @@ -52,7 +59,10 @@ public Result OnStartup(UIControlledApplication application) speckleButton2.SetContextualHelp(new ContextualHelp(ContextualHelpType.Url, "https://speckle.systems")); } - var schedulerButton = specklePanel.AddItem(new PushButtonData("Scheduler", "Scheduler", typeof(App).Assembly.Location, typeof(SchedulerCommand).FullName)) as PushButton; + var schedulerButton = + specklePanel.AddItem( + new PushButtonData("Scheduler", "Scheduler", typeof(App).Assembly.Location, typeof(SchedulerCommand).FullName) + ) as PushButton; if (schedulerButton != null) { @@ -64,37 +74,50 @@ public Result OnStartup(UIControlledApplication application) schedulerButton.SetContextualHelp(new ContextualHelp(ContextualHelpType.Url, "https://speckle.systems")); } - PulldownButton helpPulldown = specklePanel.AddItem(new PulldownButtonData("Help&Resources", "Help & Resources")) as PulldownButton; + PulldownButton helpPulldown = + specklePanel.AddItem(new PulldownButtonData("Help&Resources", "Help & Resources")) as PulldownButton; helpPulldown.Image = LoadPngImgSource("Speckle.ConnectorRevit.Assets.help16.png", path); helpPulldown.LargeImage = LoadPngImgSource("Speckle.ConnectorRevit.Assets.help32.png", path); - PushButton forum = helpPulldown.AddPushButton(new PushButtonData("forum", "Community Forum", typeof(App).Assembly.Location, typeof(ForumCommand).FullName)) as PushButton; + PushButton forum = + helpPulldown.AddPushButton( + new PushButtonData("forum", "Community Forum", typeof(App).Assembly.Location, typeof(ForumCommand).FullName) + ) as PushButton; forum.ToolTip = "Check out our Community Forum! Opens a page in your web browser."; forum.Image = LoadPngImgSource("Speckle.ConnectorRevit.Assets.forum16.png", path); forum.LargeImage = LoadPngImgSource("Speckle.ConnectorRevit.Assets.forum32.png", path); - PushButton tutorials = helpPulldown.AddPushButton(new PushButtonData("tutorials", "Tutorials", typeof(App).Assembly.Location, typeof(TutorialsCommand).FullName)) as PushButton; + PushButton tutorials = + helpPulldown.AddPushButton( + new PushButtonData("tutorials", "Tutorials", typeof(App).Assembly.Location, typeof(TutorialsCommand).FullName) + ) as PushButton; tutorials.ToolTip = "Check out our tutorials! Opens a page in your web browser."; tutorials.Image = LoadPngImgSource("Speckle.ConnectorRevit.Assets.tutorials16.png", path); tutorials.LargeImage = LoadPngImgSource("Speckle.ConnectorRevit.Assets.tutorials32.png", path); - PushButton docs = helpPulldown.AddPushButton(new PushButtonData("docs", "Docs", typeof(App).Assembly.Location, typeof(DocsCommand).FullName)) as PushButton; + PushButton docs = + helpPulldown.AddPushButton( + new PushButtonData("docs", "Docs", typeof(App).Assembly.Location, typeof(DocsCommand).FullName) + ) as PushButton; docs.ToolTip = "Check out our documentation! Opens a page in your web browser."; docs.Image = LoadPngImgSource("Speckle.ConnectorRevit.Assets.docs16.png", path); docs.LargeImage = LoadPngImgSource("Speckle.ConnectorRevit.Assets.docs32.png", path); - PushButton manager = helpPulldown.AddPushButton(new PushButtonData("manager", "Manager", typeof(App).Assembly.Location, typeof(ManagerCommand).FullName)) as PushButton; + PushButton manager = + helpPulldown.AddPushButton( + new PushButtonData("manager", "Manager", typeof(App).Assembly.Location, typeof(ManagerCommand).FullName) + ) as PushButton; manager.ToolTip = "Manage accounts and connectors. Opens SpeckleManager."; manager.Image = LoadPngImgSource("Speckle.ConnectorRevit.Assets.logo16.png", path); manager.LargeImage = LoadPngImgSource("Speckle.ConnectorRevit.Assets.logo32.png", path); - - - return Result.Succeeded; } - private void ControlledApplication_ApplicationInitialized(object sender, Autodesk.Revit.DB.Events.ApplicationInitializedEventArgs e) + private void ControlledApplication_ApplicationInitialized( + object sender, + Autodesk.Revit.DB.Events.ApplicationInitializedEventArgs e + ) { try { @@ -132,7 +155,8 @@ private void ControlledApplication_ApplicationInitialized(object sender, Autodes } else { - td.MainContent = $"Oh no! Something went wrong while loading Speckle, please report it on the forum:\n\n{ex.Message}"; + td.MainContent = + $"Oh no! Something went wrong while loading Speckle, please report it on the forum:\n\n{ex.Message}"; } td.AddCommandLink(TaskDialogCommandLinkId.CommandLink1, "Ask for help on our Community Forum"); @@ -148,21 +172,34 @@ private void ControlledApplication_ApplicationInitialized(object sender, Autodes private void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e) { - SpeckleLog.Logger.Fatal(e.Exception, "Caught thread exception with message {exceptionMessage}", e.Exception.Message); + SpeckleLog.Logger.Fatal( + e.Exception, + "Caught thread exception with message {exceptionMessage}", + e.Exception.Message + ); } private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) { if (e.ExceptionObject is Exception ex) { - SpeckleLog.Logger.Fatal(ex, "Caught unhandled exception. Is terminating : {isTerminating}. Message : {exceptionMessage}", e.IsTerminating, ex.Message); + SpeckleLog.Logger.Fatal( + ex, + "Caught unhandled exception. Is terminating : {isTerminating}. Message : {exceptionMessage}", + e.IsTerminating, + ex.Message + ); } else { - SpeckleLog.Logger.Fatal("Caught unhandled exception. Is terminating : {isTerminating}. Exception object is of type : {exceptionObjectType}. Exception object to string : {exceptionObjToString}", e.IsTerminating, e.ExceptionObject.GetType(), e.ExceptionObject.ToString()); + SpeckleLog.Logger.Fatal( + "Caught unhandled exception. Is terminating : {isTerminating}. Exception object is of type : {exceptionObjectType}. Exception object to string : {exceptionObjToString}", + e.IsTerminating, + e.ExceptionObject.GetType(), + e.ExceptionObject.ToString() + ); } } - public Result OnShutdown(UIControlledApplication application) { @@ -175,7 +212,11 @@ private ImageSource LoadPngImgSource(string sourceName, string path) { var assembly = Assembly.LoadFrom(Path.Combine(path)); var icon = assembly.GetManifestResourceStream(sourceName); - PngBitmapDecoder m_decoder = new PngBitmapDecoder(icon, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default); + PngBitmapDecoder m_decoder = new PngBitmapDecoder( + icon, + BitmapCreateOptions.PreservePixelFormat, + BitmapCacheOption.Default + ); ImageSource m_source = m_decoder.Frames[0]; return (m_source); } @@ -198,5 +239,4 @@ static Assembly OnAssemblyResolve(object sender, ResolveEventArgs args) return a; } } - } diff --git a/ConnectorRevit/ConnectorRevit/Storage/StreamStateCache.cs b/ConnectorRevit/ConnectorRevit/Storage/StreamStateCache.cs index 204eedfc08..3636dbc7d7 100644 --- a/ConnectorRevit/ConnectorRevit/Storage/StreamStateCache.cs +++ b/ConnectorRevit/ConnectorRevit/Storage/StreamStateCache.cs @@ -12,6 +12,7 @@ public class StreamStateCache : IReceivedObjectIdMap { private StreamState streamState; private Dictionary previousContextObjects; + public StreamStateCache(StreamState state) { streamState = state; @@ -34,15 +35,14 @@ public void AddConvertedElements(IConvertedObjectsCache converted var elements = convertedObjects.GetCreatedObjectsFromConvertedId(@base.applicationId).ToList(); var appObj = new ApplicationObject(@base.id, @base.speckle_type); - newContextObjects.Add(new ApplicationObject(@base.id, @base.speckle_type) - { - applicationId = @base.applicationId, - CreatedIds = elements - .Where(element => element.IsValidObject) - .Select(element => element.UniqueId) - .ToList(), - Converted = elements.Cast().ToList() - }); + newContextObjects.Add( + new ApplicationObject(@base.id, @base.speckle_type) + { + applicationId = @base.applicationId, + CreatedIds = elements.Where(element => element.IsValidObject).Select(element => element.UniqueId).ToList(), + Converted = elements.Cast().ToList() + } + ); } streamState.ReceivedObjects = newContextObjects; } diff --git a/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit .Settings.cs b/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit .Settings.cs index b3fb789534..17534430f4 100644 --- a/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit .Settings.cs +++ b/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit .Settings.cs @@ -31,23 +31,89 @@ public override List GetSettings() List mappingOptions = new List() { noMapping, everyReceive, forNewTypes }; // find project base point and survey point. these don't always have name props, so store them under custom strings - var basePoint = new FilteredElementCollector(CurrentDoc.Document).OfClass(typeof(BasePoint)).Cast().Where(o => o.IsShared == false).FirstOrDefault(); + var basePoint = new FilteredElementCollector(CurrentDoc.Document) + .OfClass(typeof(BasePoint)) + .Cast() + .FirstOrDefault(o => !o.IsShared); if (basePoint != null) referencePoints.Add(ProjectBase); - var surveyPoint = new FilteredElementCollector(CurrentDoc.Document).OfClass(typeof(BasePoint)).Cast().Where(o => o.IsShared == true).FirstOrDefault(); + var surveyPoint = new FilteredElementCollector(CurrentDoc.Document) + .OfClass(typeof(BasePoint)) + .Cast() + .FirstOrDefault(o => o.IsShared); if (surveyPoint != null) referencePoints.Add(Survey); return new List { - new ListBoxSetting {Slug = "reference-point", Name = "Reference Point", Icon ="LocationSearching", Values = referencePoints, Selection = InternalOrigin, Description = "Sends or receives stream objects in relation to this document point"}, - new CheckBoxSetting {Slug = "linkedmodels-send", Name = "Send Linked Models", Icon ="Link", IsChecked= false, Description = "Include Linked Models in the selection filters when sending"}, - new CheckBoxSetting {Slug = "linkedmodels-receive", Name = "Receive Linked Models", Icon ="Link", IsChecked= false, Description = "Include Linked Models when receiving NOTE: elements from linked models will be received in the current document"}, - new CheckBoxSetting {Slug = "recieve-objects-mesh", Name = "Receive Objects as Direct Mesh", Icon = "Link", IsChecked = false, Description = "Recieve the stream as a Meshes only"}, - new MultiSelectBoxSetting { Slug = "disallow-join", Name = "Disallow Join For Elements", Icon = "CallSplit", Description = "Determine which objects should not be allowed to join by default when receiving", - Values = new List() { ArchitecturalWalls, StructuralWalls, StructuralFraming } }, - new ListBoxSetting {Slug = "pretty-mesh", Name = "Mesh Import Method", Icon ="ChartTimelineVarient", Values = prettyMeshOptions, Selection = defaultValue, Description = "Determines the display style of imported meshes"}, - new MappingSetting {Slug = "receive-mappings", Name = "Custom Type Mapping", Icon ="LocationSearching", Values = mappingOptions, Description = "Determine how incoming object types are mapped to object types in the host application"}, + new ListBoxSetting + { + Slug = "reference-point", + Name = "Reference Point", + Icon = "LocationSearching", + Values = referencePoints, + Selection = InternalOrigin, + Description = "Sends or receives stream objects in relation to this document point" + }, + new CheckBoxSetting + { + Slug = "linkedmodels-send", + Name = "Send Linked Models", + Icon = "Link", + IsChecked = false, + Description = "Include Linked Models in the selection filters when sending" + }, + new CheckBoxSetting + { + Slug = "linkedmodels-receive", + Name = "Receive Linked Models", + Icon = "Link", + IsChecked = false, + Description = + "Include Linked Models when receiving NOTE: elements from linked models will be received in the current document" + }, + // new CheckBoxSetting + // { + // Slug = "recieve-objects-mesh", + // Name = "Receive Objects as DirectShape", + // Icon = "Link", + // IsChecked = false, + // Description = "Receive the stream as a Meshes only" + // }, + new ListBoxSetting + { + Slug = "direct-shape-strategy", + Name = "Convert received objects to DirectShape", + Icon = "Link", + Values = new List { "Always", "On Error", "Never" }, + Selection = "On Error", + Description = "Determines when to attempt conversion of an element into a DirectShape" + }, + new MultiSelectBoxSetting + { + Slug = "disallow-join", + Name = "Disallow Join For Elements", + Icon = "CallSplit", + Description = "Determine which objects should not be allowed to join by default when receiving", + Values = new List() { ArchitecturalWalls, StructuralWalls, StructuralFraming } + }, + new ListBoxSetting + { + Slug = "pretty-mesh", + Name = "Mesh Import Method", + Icon = "ChartTimelineVarient", + Values = prettyMeshOptions, + Selection = defaultValue, + Description = "Determines the display style of imported meshes" + }, + new MappingSetting + { + Slug = "receive-mappings", + Name = "Custom Type Mapping", + Icon = "LocationSearching", + Values = mappingOptions, + Description = "Determine how incoming object types are mapped to object types in the host application" + }, }; } } diff --git a/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Previews.cs b/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Previews.cs index 2a60475626..d81e4cdcc1 100644 --- a/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Previews.cs +++ b/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Previews.cs @@ -35,7 +35,7 @@ public override async Task PreviewReceive(StreamState state, Progre if (commit.id != SelectedReceiveCommit) { - // check for converter + // check for converter var converter = KitManager.GetDefaultKit().LoadConverter(ConnectorRevitUtils.RevitAppName); converter.SetContextDocument(CurrentDoc.Document); @@ -57,18 +57,19 @@ public override async Task PreviewReceive(StreamState state, Progre progress.Report.Log(previewObj); IConvertedObjectsCache convertedObjects = null; - await APIContext.Run( - app => + await APIContext + .Run(app => { using (var t = new Transaction(CurrentDoc.Document, $"Baking stream {state.StreamId}")) { t.Start(); - convertedObjects = ConvertReceivedObjects(converter, progress, new TransactionManager(null,null)); + convertedObjects = ConvertReceivedObjects(converter, progress, new TransactionManager(null, null)); t.Commit(); } AddMultipleRevitElementServers(convertedObjects); - }); + }) + .ConfigureAwait(false); } else // just generate the log { @@ -78,11 +79,7 @@ await APIContext.Run( } catch (Exception ex) { - SpeckleLog.Logger.Error( - ex, - "Failed to preview receive: {exceptionMessage}", - ex.Message - ); + SpeckleLog.Logger.Error(ex, "Failed to preview receive: {exceptionMessage}", ex.Message); } return null; @@ -95,8 +92,9 @@ public override void ResetDocument() public void AddMultipleRevitElementServers(IConvertedObjectsCache convertedObjects) { - ExternalService directContext3DService = - ExternalServiceRegistry.GetService(ExternalServices.BuiltInExternalServices.DirectContext3DService); + ExternalService directContext3DService = ExternalServiceRegistry.GetService( + ExternalServices.BuiltInExternalServices.DirectContext3DService + ); MultiServerService msDirectContext3DService = directContext3DService as MultiServerService; IList serverIds = msDirectContext3DService.GetActiveServerIds(); @@ -153,7 +151,8 @@ public override void PreviewSend(StreamState state, ProgressViewModel progress) if (!converter.CanConvertToSpeckle(filterObj)) reportObj.Update( status: ApplicationObject.State.Skipped, - logItem: $"Sending this object type is not supported in Revit"); + logItem: $"Sending this object type is not supported in Revit" + ); else reportObj.Update(status: ApplicationObject.State.Created); progress.Report.Log(reportObj); @@ -163,11 +162,7 @@ public override void PreviewSend(StreamState state, ProgressViewModel progress) } catch (Exception ex) { - SpeckleLog.Logger.Error( - ex, - "Failed to preview send: {exceptionMessage}", - ex.Message - ); + SpeckleLog.Logger.Error(ex, "Failed to preview send: {exceptionMessage}", ex.Message); } } } diff --git a/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Receive.cs b/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Receive.cs index 61264bc8d3..39d1ea55db 100644 --- a/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Receive.cs +++ b/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Receive.cs @@ -26,13 +26,13 @@ namespace Speckle.ConnectorRevit.UI { - public partial class ConnectorBindingsRevit { public List Preview { get; set; } = new List(); public Dictionary StoredObjects = new Dictionary(); public CancellationTokenSource CurrentOperationCancellation { get; set; } + /// /// Receives a stream and bakes into the existing revit file. /// @@ -56,7 +56,12 @@ public override async Task ReceiveStream(StreamState state, Progres Commit myCommit = await ConnectorHelpers.GetCommitFromState(state, progress.CancellationToken); state.LastCommit = myCommit; Base commitObject = await ConnectorHelpers.ReceiveCommit(myCommit, state, progress); - await ConnectorHelpers.TryCommitReceived(state, myCommit, ConnectorRevitUtils.RevitAppName, progress.CancellationToken); + await ConnectorHelpers.TryCommitReceived( + state, + myCommit, + ConnectorRevitUtils.RevitAppName, + progress.CancellationToken + ); Preview.Clear(); StoredObjects.Clear(); @@ -65,7 +70,6 @@ public override async Task ReceiveStream(StreamState state, Progres foreach (var previewObj in Preview) progress.Report.Log(previewObj); - converter.ReceiveMode = state.ReceiveMode; // needs to be set for editing to work var previousObjects = new StreamStateCache(state); @@ -79,15 +83,24 @@ public override async Task ReceiveStream(StreamState state, Progres #pragma warning disable CA1031 // Do not catch general exception types try { - var elementTypeMapper = new ElementTypeMapper(converter, revitDocumentAggregateCache, Preview, StoredObjects, CurrentDoc.Document); - await elementTypeMapper.Map(state.Settings.FirstOrDefault(x => x.Slug == "receive-mappings")) + var elementTypeMapper = new ElementTypeMapper( + converter, + revitDocumentAggregateCache, + Preview, + StoredObjects, + CurrentDoc.Document + ); + await elementTypeMapper + .Map(state.Settings.FirstOrDefault(x => x.Slug == "receive-mappings")) .ConfigureAwait(false); } catch (Exception ex) { var speckleEx = new SpeckleException($"Failed to map incoming types to Revit types. Reason: {ex.Message}", ex); StreamViewModel.HandleCommandException(speckleEx, false, "MapIncomingTypesCommand"); - progress.Report.LogOperationError(new Exception("Could not update receive object with user types. Using default mapping.", ex)); + progress.Report.LogOperationError( + new Exception("Could not update receive object with user types. Using default mapping.", ex) + ); } finally { @@ -95,36 +108,39 @@ await elementTypeMapper.Map(state.Settings.FirstOrDefault(x => x.Slug == "receiv } #pragma warning restore CA1031 // Do not catch general exception types - var (success, exception) = await APIContext.Run(_ => - { - using var transactionManager = new TransactionManager(state.StreamId, CurrentDoc.Document); - transactionManager.Start(); - - try + var (success, exception) = await APIContext + .Run(_ => { - converter.SetContextDocument(transactionManager); + using var transactionManager = new TransactionManager(state.StreamId, CurrentDoc.Document); + transactionManager.Start(); - var convertedObjects = ConvertReceivedObjects(converter, progress, transactionManager); + try + { + converter.SetContextDocument(transactionManager); - if (state.ReceiveMode == ReceiveMode.Update) - DeleteObjects(previousObjects, convertedObjects); + var convertedObjects = ConvertReceivedObjects(converter, progress, transactionManager); - previousObjects.AddConvertedElements(convertedObjects); - transactionManager.Finish(); - return (true, null); - } - catch (Exception ex) - { - SpeckleLog.Logger.Error(ex, "Rolling back connector transaction"); + if (state.ReceiveMode == ReceiveMode.Update) + DeleteObjects(previousObjects, convertedObjects); + + previousObjects.AddConvertedElements(convertedObjects); + transactionManager.Finish(); + return (true, null); + } + catch (Exception ex) + { + SpeckleLog.Logger.Error(ex, "Rolling back connector transaction"); - string message = $"Fatal Error: {ex.Message}"; - if (ex is OperationCanceledException) message = "Receive cancelled"; - progress.Report.LogOperationError(new Exception($"{message} - Changes have been rolled back", ex)); + string message = $"Fatal Error: {ex.Message}"; + if (ex is OperationCanceledException) + message = "Receive cancelled"; + progress.Report.LogOperationError(new Exception($"{message} - Changes have been rolled back", ex)); - transactionManager.RollbackAll(); - return (false, ex); //We can't throw exceptions in from RevitTask, but we can return it along with a success status - } - }).ConfigureAwait(false); + transactionManager.RollbackAll(); + return (false, ex); //We can't throw exceptions in from RevitTask, but we can return it along with a success status + } + }) + .ConfigureAwait(false); revitDocumentAggregateCache.InvalidateAll(); CurrentOperationCancellation = null; @@ -145,10 +161,13 @@ await elementTypeMapper.Map(state.Settings.FirstOrDefault(x => x.Slug == "receiv } //delete previously sent object that are no more in this stream - private void DeleteObjects(IReceivedObjectIdMap previousObjects, IConvertedObjectsCache convertedObjects) + private void DeleteObjects( + IReceivedObjectIdMap previousObjects, + IConvertedObjectsCache convertedObjects + ) { var previousAppIds = previousObjects.GetAllConvertedIds().ToList(); - for (var i = previousAppIds.Count - 1; i >=0; i--) + for (var i = previousAppIds.Count - 1; i >= 0; i--) { var appId = previousAppIds[i]; if (string.IsNullOrEmpty(appId) || convertedObjects.HasConvertedObjectWithId(appId)) @@ -177,13 +196,61 @@ private void DeleteObjects(IReceivedObjectIdMap previousObjects, } } - private IConvertedObjectsCache ConvertReceivedObjects(ISpeckleConverter converter, ProgressViewModel progress, TransactionManager transactionManager) + private IConvertedObjectsCache ConvertReceivedObjects( + ISpeckleConverter converter, + ProgressViewModel progress, + TransactionManager transactionManager + ) { + // Traverses through the `elements` property of the given base + void ConvertNestedElements(Base @base, ApplicationObject appObj, bool receiveDirectMesh) + { + if (@base == null) + return; + + var nestedElements = @base["elements"] ?? @base["@elements"]; + + if (nestedElements == null) + return; + + // set host in converter state. + // assumes host is the first converted object of the appObject + var host = appObj == null || !appObj.Converted.Any() ? null : appObj.Converted.First() as Element; + using var ctx = RevitConverterState.Push(); + ctx.CurrentHostElement = host; + + // traverse each element member and convert + foreach (var obj in GraphTraversal.TraverseMember(nestedElements)) + { + // create the application object and log to reports + var nestedAppObj = Preview.Where(o => o.OriginalId == obj.id)?.FirstOrDefault(); + if (nestedAppObj == null) + { + nestedAppObj = new ApplicationObject(obj.id, ConnectorRevitUtils.SimplifySpeckleType(obj.speckle_type)) + { + applicationId = obj.applicationId, + Convertible = converter.CanConvertToNative(obj) + }; + progress.Report.Log(nestedAppObj); + converter.Report.Log(nestedAppObj); + } + + // convert + var converted = ConvertObject(nestedAppObj, obj, receiveDirectMesh, converter, progress, transactionManager); + + // Check if parent conversion succeeded before attempting the children + if ( + receiveDirectMesh || converted?.Status is ApplicationObject.State.Created or ApplicationObject.State.Updated + ) + // recurse and convert nested elements + ConvertNestedElements(obj, nestedAppObj, receiveDirectMesh); + } + } + using var _d0 = LogContext.PushProperty("converterName", converter.Name); using var _d1 = LogContext.PushProperty("converterAuthor", converter.Author); using var _d2 = LogContext.PushProperty("conversionDirection", nameof(ISpeckleConverter.ConvertToNative)); - var convertedObjectsCache = new ConvertedObjectsCache(); converter.SetContextDocument(convertedObjectsCache); @@ -191,9 +258,29 @@ private IConvertedObjectsCache ConvertReceivedObjects(ISpeckleCon conversionProgressDict["Conversion"] = 1; // Get setting to skip linked model elements if necessary - var receiveLinkedModelsSetting = CurrentSettings.FirstOrDefault(x => x.Slug == "linkedmodels-receive") as CheckBoxSetting; - var receiveLinkedModels = receiveLinkedModelsSetting != null ? receiveLinkedModelsSetting.IsChecked : false; + var receiveLinkedModelsSetting = + CurrentSettings?.FirstOrDefault(x => x.Slug == "linkedmodels-receive") as CheckBoxSetting; + var receiveLinkedModels = receiveLinkedModelsSetting?.IsChecked ?? false; + + var receiveDirectMesh = false; + var fallbackToDirectShape = false; + var directShapeStrategySetting = + CurrentSettings?.FirstOrDefault(x => x.Slug == "direct-shape-strategy") as ListBoxSetting; + switch (directShapeStrategySetting!.Selection) + { + case "Always": + receiveDirectMesh = true; + break; + case "On Error": + fallbackToDirectShape = true; + break; + case "Never": + case null: + // Do nothing, default values will do. + break; + } + // convert var index = -1; while (++index < Preview.Count) { @@ -202,69 +289,164 @@ private IConvertedObjectsCache ConvertReceivedObjects(ISpeckleCon var @base = StoredObjects[obj.OriginalId]; - using var _d3 = LogContext.PushProperty("speckleType", @base.speckle_type); - try + // skip if this object has already been converted from a nested elements loop + if (obj.Status != ApplicationObject.State.Unknown) + continue; + + conversionProgressDict["Conversion"]++; + progress.Update(conversionProgressDict); + + //skip element if is from a linked file and setting is off + if ( + !receiveLinkedModels + && @base["isRevitLinkedModel"] != null + && bool.Parse(@base["isRevitLinkedModel"].ToString()) + ) + continue; + + var converted = ConvertObject(obj, @base, receiveDirectMesh, converter, progress, transactionManager); + // Determine if we should use the fallback DirectShape conversion + // Should only happen when receiveDirectMesh is OFF, fallback is ON and object failed normal conversion. + bool usingFallback = + !receiveDirectMesh && fallbackToDirectShape && converted.Status == ApplicationObject.State.Failed; + if (usingFallback) { - conversionProgressDict["Conversion"]++; - progress.Update(conversionProgressDict); + obj.Log.Add("Conversion to native Revit object failed. Retrying conversion with displayable geometry."); + converted = ConvertObject(obj, @base, true, converter, progress, transactionManager); + if (converted == null) + obj.Update(status: ApplicationObject.State.Failed, logItem: "Conversion returned null."); + } + + RefreshView(); + if (index % 50 == 0) + transactionManager.Commit(); + + // Check if parent conversion succeeded or fallback is enabled before attempting the children + if ( + usingFallback + || receiveDirectMesh + || converted?.Status is ApplicationObject.State.Created or ApplicationObject.State.Updated + ) + // continue traversing for hosted elements + // use DirectShape conversion if the parent was converted using fallback or if the global setting is active. + ConvertNestedElements(@base, converted, usingFallback || receiveDirectMesh); + } + + return convertedObjectsCache; + } - var s = new CancellationTokenSource(); - DispatcherTimer.RunOnce(() => s.Cancel(), TimeSpan.FromMilliseconds(10)); - Dispatcher.UIThread.MainLoop(s.Token); + private ApplicationObject ConvertObject( + ApplicationObject obj, + Base @base, + bool receiveDirectMesh, + ISpeckleConverter converter, + ProgressViewModel progress, + TransactionManager transactionManager + ) + { + progress.CancellationToken.ThrowIfCancellationRequested(); - //skip element if is from a linked file and setting is off - if (!receiveLinkedModels && @base["isRevitLinkedModel"] != null && bool.Parse(@base["isRevitLinkedModel"].ToString())) - continue; + if (obj == null || @base == null) + return obj; - transactionManager.StartSubtransaction(); - var convRes = converter.ConvertToNative(@base); - transactionManager.CommitSubtransaction(); + using var _d3 = LogContext.PushProperty("speckleType", @base.speckle_type); + transactionManager.StartSubtransaction(); - RefreshView(); - if (index % 50 == 0) - transactionManager.Commit(); + try + { + var s = new CancellationTokenSource(); + DispatcherTimer.RunOnce(() => s.Cancel(), TimeSpan.FromMilliseconds(10)); + Dispatcher.UIThread.MainLoop(s.Token); - switch (convRes) - { - case ApplicationObject o: - obj.Update(status: o.Status, createdIds: o.CreatedIds, converted: o.Converted, log: o.Log); - progress.Report.UpdateReportObject(obj); - break; - default: - break; - } - } - catch (ConversionNotReadyException ex) + ApplicationObject convRes; + if (converter.CanConvertToNative(@base)) { - transactionManager.RollbackSubTransaction(); - var notReadyDataCache = revitDocumentAggregateCache - .GetOrInitializeEmptyCacheOfType(out _); - var notReadyData = notReadyDataCache - .GetOrAdd(@base.id, () => new ConversionNotReadyCacheData(), out _); - - if (++notReadyData.NumberOfTimesCaught > 2) + if (receiveDirectMesh) { - SpeckleLog.Logger.Warning(ex, $"Speckle object of type {@base.GetType()} was waiting for an object to convert that never did"); - obj.Update(status: ApplicationObject.State.Failed, logItem: ex.Message); - progress.Report.UpdateReportObject(obj); + convRes = converter.ConvertToNativeDisplayable(@base) as ApplicationObject; + if (convRes == null) + { + obj.Update(status: ApplicationObject.State.Failed, logItem: "Conversion returned null."); + return obj; + } } else { - Preview.Add(obj); + convRes = converter.ConvertToNative(@base) as ApplicationObject; + if (convRes == null || convRes.Status == ApplicationObject.State.Failed) + { + var logItem = + convRes == null + ? "Conversion returned null" + : "Conversion failed with errors: " + string.Join("/n", convRes.Log); + obj.Update(status: ApplicationObject.State.Failed, logItem: logItem); + return obj; + } + } + } + else if (converter.CanConvertToNativeDisplayable(@base)) + { + obj.Log.Add("No direct conversion exists. Converting displayable geometry."); + convRes = converter.ConvertToNativeDisplayable(@base) as ApplicationObject; + if (convRes == null) + { + obj.Update(status: ApplicationObject.State.Failed, logItem: "Conversion returned null."); + return obj; } - // the struct must be saved to the cache again or the "numberOfTimesCaught" increment will not persist - notReadyDataCache.Set(@base.id, notReadyData); } - catch (Exception ex) + else { - transactionManager.RollbackSubTransaction(); - SpeckleLog.Logger.Warning(ex, "Failed to convert"); + obj.Update( + status: ApplicationObject.State.Skipped, + logItem: "No direct conversion or displayable values can be converted." + ); + return obj; + } + + obj.Update( + status: convRes.Status, + createdIds: convRes.CreatedIds, + converted: convRes.Converted, + log: convRes.Log + ); + + progress.Report.UpdateReportObject(obj); + RefreshView(); + transactionManager.CommitSubtransaction(); + } + catch (ConversionNotReadyException ex) + { + transactionManager.RollbackSubTransaction(); + var notReadyDataCache = + revitDocumentAggregateCache.GetOrInitializeEmptyCacheOfType(out _); + var notReadyData = notReadyDataCache.GetOrAdd(@base.id, () => new ConversionNotReadyCacheData(), out _); + + if (++notReadyData.NumberOfTimesCaught > 2) + { + SpeckleLog.Logger.Warning( + ex, + $"Speckle object of type {@base.GetType()} was waiting for an object to convert that never did" + ); obj.Update(status: ApplicationObject.State.Failed, logItem: ex.Message); progress.Report.UpdateReportObject(obj); } + else + { + Preview.Add(obj); + } + // the struct must be saved to the cache again or the "numberOfTimesCaught" increment will not persist + notReadyDataCache.Set(@base.id, notReadyData); + } + catch (Exception ex) + { + transactionManager.RollbackSubTransaction(); + SpeckleLog.Logger.Warning(ex, "Failed to convert due to unexpected error."); + obj.Update(status: ApplicationObject.State.Failed, logItem: "Failed to convert due to unexpected error."); + obj.Log.Add($"{ex.Message}"); + progress.Report.UpdateReportObject(obj); } - return convertedObjectsCache; + return obj; } private void RefreshView() @@ -293,26 +475,31 @@ private void RefreshView() /// A flattened list of objects to be converted ToNative private List FlattenCommitObject(Base obj, ISpeckleConverter converter) { - ApplicationObject CreateApplicationObject(Base current) { - if (!converter.CanConvertToNative(current)) return null; + // determine if this object is displayable + var isDisplayable = DefaultTraversal.displayValuePropAliases.Any(o => current[o] != null); + + // skip if this object was already stored, if it's not convertible and has no displayables + if (StoredObjects.ContainsKey(current.id)) + return null; + if (!converter.CanConvertToNative(current) && !isDisplayable) + return null; + // create application object and store var appObj = new ApplicationObject(current.id, ConnectorRevitUtils.SimplifySpeckleType(current.speckle_type)) { applicationId = current.applicationId, - Convertible = true + Convertible = converter.CanConvertToNative(current) }; - if (StoredObjects.ContainsKey(current.id)) - return null; - StoredObjects.Add(current.id, current); return appObj; } var traverseFunction = DefaultTraversal.CreateRevitTraversalFunc(converter); - var objectsToConvert = traverseFunction.Traverse(obj) + var objectsToConvert = traverseFunction + .Traverse(obj) .Select(tc => CreateApplicationObject(tc.current)) .Where(appObject => appObject != null) .Reverse() @@ -320,6 +507,5 @@ ApplicationObject CreateApplicationObject(Base current) return objectsToConvert; } - } } diff --git a/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Send.cs b/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Send.cs index 1fe351fe2a..3f55612e49 100644 --- a/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Send.cs +++ b/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Send.cs @@ -34,6 +34,8 @@ public partial class ConnectorBindingsRevit /// StreamState passed by the UI public override async Task SendStream(StreamState state, ProgressViewModel progress) { + using var ctx = RevitConverterState.Push(); + //make sure to instance a new copy so all values are reset correctly var converter = (ISpeckleConverter)Activator.CreateInstance(Converter.GetType()); converter.SetContextDocument(CurrentDoc.Document); @@ -67,7 +69,9 @@ public override async Task SendStream(StreamState state, ProgressViewMod if (converter is not IRevitCommitObjectBuilderExposer builderExposer) { - throw new Exception($"Converter {converter.Name} by {converter.Author} does not provide the necessary object, {nameof(IRevitCommitObjectBuilder)}, needed to build the Speckle commit object."); + throw new Exception( + $"Converter {converter.Name} by {converter.Author} does not provide the necessary object, {nameof(IRevitCommitObjectBuilder)}, needed to build the Speckle commit object." + ); } else { @@ -80,71 +84,72 @@ public override async Task SendStream(StreamState state, ProgressViewMod var conversionProgressDict = new ConcurrentDictionary { ["Conversion"] = 0 }; var convertedCount = 0; - await APIContext.Run(() => - { - using var _d0 = LogContext.PushProperty("converterName", converter.Name); - using var _d1 = LogContext.PushProperty("converterAuthor", converter.Author); - using var _d2 = LogContext.PushProperty("conversionDirection", nameof(ISpeckleConverter.ConvertToSpeckle)); - - foreach (var revitElement in selectedObjects) + await APIContext + .Run(() => { - if (progress.CancellationToken.IsCancellationRequested) - break; - - bool isAlreadyConverted = GetOrCreateApplicationObject( - revitElement, - converter.Report, - out ApplicationObject reportObj - ); - if (isAlreadyConverted) - continue; + using var _d0 = LogContext.PushProperty("converterName", converter.Name); + using var _d1 = LogContext.PushProperty("converterAuthor", converter.Author); + using var _d2 = LogContext.PushProperty("conversionDirection", nameof(ISpeckleConverter.ConvertToSpeckle)); - progress.Report.Log(reportObj); + foreach (var revitElement in selectedObjects) + { + if (progress.CancellationToken.IsCancellationRequested) + break; - //Add context to logger - using var _d3 = LogContext.PushProperty("elementType", revitElement.GetType()); - using var _d4 = LogContext.PushProperty("elementCategory", revitElement.Category?.Name); + bool isAlreadyConverted = GetOrCreateApplicationObject( + revitElement, + converter.Report, + out ApplicationObject reportObj + ); + if (isAlreadyConverted) + continue; - try - { - converter.Report.Log(reportObj); // Log object so converter can access + progress.Report.Log(reportObj); - Base result = ConvertToSpeckle(revitElement, converter); + //Add context to logger + using var _d3 = LogContext.PushProperty("elementType", revitElement.GetType()); + using var _d4 = LogContext.PushProperty("elementCategory", revitElement.Category?.Name); - reportObj.Update( - status: ApplicationObject.State.Created, - logItem: $"Sent as {ConnectorRevitUtils.SimplifySpeckleType(result.speckle_type)}" - ); - if (result.applicationId != reportObj.applicationId) + try { - SpeckleLog.Logger.Information( - "Conversion result of type {elementType} has a different application Id ({actualId}) to the report object {expectedId}", - revitElement.GetType(), - result.applicationId, - reportObj.applicationId + converter.Report.Log(reportObj); // Log object so converter can access + + Base result = ConvertToSpeckle(revitElement, converter); + + reportObj.Update( + status: ApplicationObject.State.Created, + logItem: $"Sent as {ConnectorRevitUtils.SimplifySpeckleType(result.speckle_type)}" ); - result.applicationId = reportObj.applicationId; + if (result.applicationId != reportObj.applicationId) + { + SpeckleLog.Logger.Information( + "Conversion result of type {elementType} has a different application Id ({actualId}) to the report object {expectedId}", + revitElement.GetType(), + result.applicationId, + reportObj.applicationId + ); + result.applicationId = reportObj.applicationId; + } + commitObjectBuilder.IncludeObject(result, revitElement); + convertedCount++; + } + catch (ConversionSkippedException ex) + { + reportObj.Update(status: ApplicationObject.State.Skipped, logItem: ex.Message); + } + catch (Exception ex) + { + SpeckleLog.Logger.Error(ex, "Object failed during conversion"); + reportObj.Update(status: ApplicationObject.State.Failed, logItem: $"{ex.Message}"); } - commitObjectBuilder.IncludeObject(result, revitElement); - convertedCount++; - } - catch (ConversionSkippedException ex) - { - reportObj.Update(status: ApplicationObject.State.Skipped, logItem: ex.Message); - } - catch (Exception ex) - { - SpeckleLog.Logger.Error(ex, "Object failed during conversion"); - reportObj.Update(status: ApplicationObject.State.Failed, logItem: $"{ex.Message}"); - } - conversionProgressDict["Conversion"]++; - progress.Update(conversionProgressDict); + conversionProgressDict["Conversion"]++; + progress.Update(conversionProgressDict); - YieldToUIThread(TimeSpan.FromMilliseconds(1)); - } - }) - .ConfigureAwait(false); + YieldToUIThread(TimeSpan.FromMilliseconds(1)); + } + }) + .ConfigureAwait(false); revitDocumentAggregateCache.InvalidateAll(); @@ -213,6 +218,7 @@ out ApplicationObject reportObj } private DateTime timerStarted = DateTime.MinValue; + private void YieldToUIThread(TimeSpan delay) { var currentTime = DateTime.UtcNow; diff --git a/ConnectorRevit/ConnectorRevit2022/ConnectorRevit2022.csproj b/ConnectorRevit/ConnectorRevit2022/ConnectorRevit2022.csproj index 639ce9b0e4..dcedeef4cb 100644 --- a/ConnectorRevit/ConnectorRevit2022/ConnectorRevit2022.csproj +++ b/ConnectorRevit/ConnectorRevit2022/ConnectorRevit2022.csproj @@ -39,7 +39,7 @@ - + diff --git a/ConnectorRevit/RevitSharedResources/Models/RevitConverterState.cs b/ConnectorRevit/RevitSharedResources/Models/RevitConverterState.cs new file mode 100644 index 0000000000..ca50de316a --- /dev/null +++ b/ConnectorRevit/RevitSharedResources/Models/RevitConverterState.cs @@ -0,0 +1,10 @@ +#nullable enable +using Autodesk.Revit.DB; +using Speckle.Core.Helpers; + +namespace RevitSharedResources.Models; + +public class RevitConverterState : State +{ + public Element? CurrentHostElement { get; set; } +} diff --git a/ConnectorRevit/RevitSharedResources/Models/TransactionManager.cs b/ConnectorRevit/RevitSharedResources/Models/TransactionManager.cs index 0ce3950a8a..fad2aad1a3 100644 --- a/ConnectorRevit/RevitSharedResources/Models/TransactionManager.cs +++ b/ConnectorRevit/RevitSharedResources/Models/TransactionManager.cs @@ -14,6 +14,7 @@ public class TransactionManager : IDisposable { private string streamId; private Document document; + public TransactionManager(string streamId, Document document) { this.streamId = streamId; @@ -50,9 +51,7 @@ public void Start() transactionGroup.Start(); } - if (transaction == null - || !transaction.IsValidObject - || transaction.GetStatus() != TransactionStatus.Started) + if (transaction == null || !transaction.IsValidObject || transaction.GetStatus() != TransactionStatus.Started) { transaction = new Transaction(document, $"Baking stream {streamId}"); var failOpts = transaction.GetFailureHandlingOptions(); @@ -66,16 +65,16 @@ public void Start() public TransactionStatus Commit() { - if (subTransaction != null - && subTransaction.IsValidObject - && subTransaction.GetStatus() == TransactionStatus.Started) + if ( + subTransaction != null + && subTransaction.IsValidObject + && subTransaction.GetStatus() == TransactionStatus.Started + ) { HandleFailedCommit(subTransaction.Commit()); subTransaction.Dispose(); } - if (transaction != null - && transaction.IsValidObject - && transaction.GetStatus() == TransactionStatus.Started) + if (transaction != null && transaction.IsValidObject && transaction.GetStatus() == TransactionStatus.Started) { var status = transaction.Commit(); HandleFailedCommit(status); @@ -94,30 +93,42 @@ private void HandleFailedCommit(TransactionStatus status) var exception = errorEater.GetException(); if (exception == null) - SpeckleLog.Logger.Fatal("Revit commit failed with {numUniqueErrors} unique errors and {numTotalErrors} total errors, but the ErrorEater did not capture any exceptions", numUniqueErrors, numTotalErrors); + SpeckleLog.Logger.Fatal( + "Revit commit failed with {numUniqueErrors} unique errors and {numTotalErrors} total errors, but the ErrorEater did not capture any exceptions", + numUniqueErrors, + numTotalErrors + ); else - SpeckleLog.Logger.Fatal(exception, "The Revit API could not resolve {numUniqueErrors} unique errors and {numTotalErrors} total errors when trying to commit the Speckle model. The whole transaction is being rolled back.", numUniqueErrors, numTotalErrors); - - throw exception ?? new SpeckleException($"The Revit API could not resolve {numUniqueErrors} unique errors and {numTotalErrors} total errors when trying to commit the Speckle model. The whole transaction is being rolled back."); + SpeckleLog.Logger.Fatal( + exception, + "The Revit API could not resolve {numUniqueErrors} unique errors and {numTotalErrors} total errors when trying to commit the Speckle model. The whole transaction is being rolled back.", + numUniqueErrors, + numTotalErrors + ); + + throw exception + ?? new SpeckleException( + $"The Revit API could not resolve {numUniqueErrors} unique errors and {numTotalErrors} total errors when trying to commit the Speckle model. The whole transaction is being rolled back." + ); } } public void RollbackTransaction() { RollbackSubTransaction(); - if (transaction != null - && transaction.IsValidObject - && transaction.GetStatus() == TransactionStatus.Started) + if (transaction != null && transaction.IsValidObject && transaction.GetStatus() == TransactionStatus.Started) { transaction.RollBack(); } } - + public void RollbackSubTransaction() { - if (subTransaction != null - && subTransaction.IsValidObject - && subTransaction.GetStatus() == TransactionStatus.Started) + if ( + subTransaction != null + && subTransaction.IsValidObject + && subTransaction.GetStatus() == TransactionStatus.Started + ) { subTransaction.RollBack(); } @@ -126,9 +137,11 @@ public void RollbackSubTransaction() public void RollbackAll() { RollbackTransaction(); - if (transactionGroup != null - && transactionGroup.IsValidObject - && transactionGroup.GetStatus() == TransactionStatus.Started) + if ( + transactionGroup != null + && transactionGroup.IsValidObject + && transactionGroup.GetStatus() == TransactionStatus.Started + ) { transactionGroup.Assimilate(); } @@ -137,9 +150,11 @@ public void RollbackAll() public void StartSubtransaction() { Start(); - if (subTransaction == null + if ( + subTransaction == null || !subTransaction.IsValidObject - || subTransaction.GetStatus() != TransactionStatus.Started) + || subTransaction.GetStatus() != TransactionStatus.Started + ) { subTransaction = new SubTransaction(document); subTransaction.Start(); @@ -162,6 +177,7 @@ public TResult ExecuteInTemporaryTransaction(Func function) { return ExecuteInTemporaryTransaction(function, document); } + public static TResult ExecuteInTemporaryTransaction(Func function, Document document) { TResult result = default; @@ -212,7 +228,8 @@ public void Dispose() protected virtual void Dispose(bool disposing) { - if (isDisposed) return; + if (isDisposed) + return; if (disposing) { diff --git a/ConnectorRevit/RevitSharedResources/RevitSharedResources.projitems b/ConnectorRevit/RevitSharedResources/RevitSharedResources.projitems index 70a9518b95..8b6f7e2603 100644 --- a/ConnectorRevit/RevitSharedResources/RevitSharedResources.projitems +++ b/ConnectorRevit/RevitSharedResources/RevitSharedResources.projitems @@ -26,10 +26,11 @@ + - \ No newline at end of file + diff --git a/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Receive.cs b/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Receive.cs index 73d4d09906..5d8f35bb88 100644 --- a/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Receive.cs +++ b/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Receive.cs @@ -346,7 +346,7 @@ ApplicationObject NewAppObj() } //Handle objects convertable using displayValues - var fallbackMember = current["displayValue"] ?? current["@displayValue"]; + var fallbackMember = DefaultTraversal.displayValuePropAliases.Where(o => current[o] != null)?.FirstOrDefault(); var parameters = current["parameters"] as Base; if (fallbackMember != null) { diff --git a/Core/Core/Models/Extensions.cs b/Core/Core/Models/Extensions.cs index 7103bab560..9f3f0d1ff5 100644 --- a/Core/Core/Models/Extensions.cs +++ b/Core/Core/Models/Extensions.cs @@ -151,4 +151,50 @@ public static string GetDetachedPropName(this Base speckleObject, string propNam { return speckleObject.GetMembers(DynamicBaseMemberType.Instance).ContainsKey(propName) ? propName : $"@{propName}"; } + + /// + /// Checks if an object "is displayable" i.e. has a displayValue property that is a list of base. + /// This is to mirror the selection logic of our viewer package, where any "displayable object" will become + /// a single selectable entity. + /// + /// The Base object to check. + /// True if the object is displayable, false otherwise. + public static bool IsDisplayableObject(this Base speckleObject) + { + return speckleObject.TryGetDisplayValue() != null; + } + + public static IEnumerable? TryGetDisplayValue(this Base obj) + where T : Base + { + var rawDisplayValue = obj["displayValue"] ?? obj["@displayValue"]; + return rawDisplayValue switch + { + T b => new List { b }, + IEnumerable enumerable => enumerable.OfType(), + _ => null + }; + } + + public static IEnumerable? TryGetDisplayValue(this Base obj) + { + return TryGetDisplayValue(obj); + } + + public static string? TryGetName(this Base obj) + { + return obj["name"] as string; + } + + public static IEnumerable? TryGetParameters(this Base obj) + where T : Base + { + var parameters = (obj["parameters"] ?? obj["@parameters"]) as Base; + return parameters?.GetMembers(DynamicBaseMemberType.Dynamic).Values.OfType(); + } + + public static IEnumerable? TryGetParameters(this Base obj) + { + return TryGetParameters(obj); + } } diff --git a/Core/Core/Models/GraphTraversal/GraphTraversal.cs b/Core/Core/Models/GraphTraversal/GraphTraversal.cs index 92bd1bb892..4b21e232c4 100644 --- a/Core/Core/Models/GraphTraversal/GraphTraversal.cs +++ b/Core/Core/Models/GraphTraversal/GraphTraversal.cs @@ -23,12 +23,14 @@ protected TraversalContext(Base current, string? propName = null) } } -public class TraversalContext : TraversalContext where T : TraversalContext +public class TraversalContext : TraversalContext + where T : TraversalContext { public override TraversalContext? parent => typedParent; public T? typedParent { get; } - public TraversalContext(Base current, string? propName = null, T? parent = default) : base(current, propName) + public TraversalContext(Base current, string? propName = null, T? parent = default) + : base(current, propName) { this.typedParent = parent; } @@ -36,7 +38,10 @@ public TraversalContext(Base current, string? propName = null, T? parent = defau public class GraphTraversal : GraphTraversal { - public GraphTraversal(params ITraversalRule[] traversalRule) : base(traversalRule) { } + public GraphTraversal(params ITraversalRule[] traversalRule) + : base(traversalRule) { } + + public static readonly string traversalContextId = "traversalContextId"; protected override TraversalContext NewContext(Base current, string? propName, TraversalContext? parent) { @@ -44,7 +49,8 @@ protected override TraversalContext NewContext(Base current, string? propName, T } } -public abstract class GraphTraversal where T : TraversalContext +public abstract class GraphTraversal + where T : TraversalContext { private readonly ITraversalRule[] rules; @@ -68,6 +74,7 @@ public IEnumerable Traverse(Base root) int headIndex = stack.Count - 1; T head = stack[headIndex]; stack.RemoveAt(headIndex); + yield return head; Base current = head.current; diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/ConversionUtils.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/ConversionUtils.cs index cc13cf19e9..2f25a49ef3 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/ConversionUtils.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/ConversionUtils.cs @@ -196,49 +196,6 @@ public IList GetHostedElementIds(Element host) return ids; } - public ApplicationObject SetHostedElements(Base @base, Element host, ApplicationObject appObj) - { - if (@base == null) - return appObj; - - //we used to use "elements" but have now switched to "@elements" - //this extra check is for backwards compatibility - var nestedElements = @base["elements"] ?? @base["@elements"]; - if (nestedElements == null) - return appObj; - - CurrentHostElement = host; - foreach (var obj in GraphTraversal.TraverseMember(nestedElements)) - { - if (!CanConvertToNative(obj)) - { - appObj.Update(logItem: $"Hosted element of type {obj.speckle_type} is not supported in Revit"); - continue; - } - - try - { - transactionManager.StartSubtransaction(); - var res = ConvertToNative(obj); - transactionManager.CommitSubtransaction(); - if (res is ApplicationObject apl) - appObj.Update(createdIds: apl.CreatedIds, converted: apl.Converted); - } - catch (Exception ex) - { - appObj.Update( - logItem: $"Failed to create hosted element {obj.speckle_type} in host ({host.Id}): \n{ex.Message}" - ); - SpeckleLog.Logger.Error(ex, ex.Message); - transactionManager.RollbackSubTransaction(); - continue; - } - CurrentHostElement = host; // set this again in case this is a deeply hosted element - } - CurrentHostElement = null; // unset the current host element. - return appObj; - } - #endregion #region parameters @@ -262,7 +219,8 @@ public void GetAllRevitParamsAndIds(Base speckleElement, DB.Element revitElement speckleElement is Level ? null : elementType, //ignore type props of levels..! allParams, true, - exclusions); + exclusions + ); Base paramBase = new(); //sort by key @@ -310,15 +268,16 @@ private void AddElementParamsToDict( DB.Element element, Dictionary paramDict, bool isTypeParameter = false, - List exclusions = null) + List exclusions = null + ) { - if (element == null) return; + if (element == null) + return; exclusions ??= new(); using var parameters = element.Parameters; foreach (DB.Parameter param in parameters) { - // exclude parameters that don't have a value and those pointing to other elements as we don't support them if (param.StorageType == StorageType.ElementId || !param.HasValue) { @@ -335,7 +294,8 @@ private void AddElementParamsToDict( param, isTypeParameter, paramInternalName: internalName, - cache: revitDocumentAggregateCache); + cache: revitDocumentAggregateCache + ); paramDict[internalName] = speckleParam; } } @@ -395,11 +355,12 @@ private static Parameter ParameterToSpeckle( } private static object GetParameterValue( - DB.Parameter rp, + DB.Parameter rp, Definition definition, out string unitType, string unitsOverride = null, - IRevitDocumentAggregateCache cache = null) + IRevitDocumentAggregateCache cache = null + ) { unitType = null; switch (rp.StorageType) @@ -409,9 +370,7 @@ private static object GetParameterValue( var val = rp.AsDouble(); var unitTypeId = unitsOverride != null ? UnitsToNative(unitsOverride) : rp.GetUnitTypeId(); unitType = UnitsToNativeString(unitTypeId); - return cache != null - ? ScaleToSpeckle(val, unitTypeId, cache) - : ScaleToSpeckleStatic(val, unitTypeId); + return cache != null ? ScaleToSpeckle(val, unitTypeId, cache) : ScaleToSpeckleStatic(val, unitTypeId); case StorageType.Integer: var intVal = rp.AsInteger(); #if REVIT2020 || REVIT2021 || REVIT2022 @@ -443,7 +402,7 @@ private static object GetParameterValue( } #endregion - + /// /// Get the symbol unit of the parameter : eg. mm, m, cm, etc. /// @@ -501,7 +460,7 @@ public static string GetSymbolUnit(DB.Parameter parameter) #endif return symbol; } - + /// /// /// @@ -657,7 +616,7 @@ private static string GetParamInternalName(DB.Parameter rp) // else // return (rp.Definition as InternalDefinition).BuiltInParameter != ; //} - + private void TrySetParam(DB.Element elem, BuiltInParameter bip, DB.Element value) { var param = elem.get_Parameter(bip); @@ -693,7 +652,7 @@ private void TrySetParam(DB.Element elem, BuiltInParameter bip, bool value) // } //} - + private void TrySetParam(DB.Element elem, BuiltInParameter bip, object value, string units = "") { var param = elem.get_Parameter(bip); @@ -704,6 +663,7 @@ private void TrySetParam(DB.Element elem, BuiltInParameter bip, object value, st TrySetParam(param, value, units); } + /// /// Queries a Revit Document for phases by the given name. /// @@ -1012,30 +972,30 @@ public string GetTemplatePath(string templateName) public IEnumerable<(string, Element, Connector)> GetRevitConnectorsThatConnectToSpeckleConnector( RevitMEPConnector revitMEPConnector, - IConvertedObjectsCache receivedObjectsCache) + IConvertedObjectsCache receivedObjectsCache + ) { var origin = PointToNative(revitMEPConnector.origin); foreach (var connectedId in revitMEPConnector.connectedConnectorIds) { var connectorAppId = connectedId.Split('.').First(); - var convertedElement = receivedObjectsCache - .GetCreatedObjectsFromConvertedId(connectorAppId) - .FirstOrDefault(); + var convertedElement = receivedObjectsCache.GetCreatedObjectsFromConvertedId(connectorAppId).FirstOrDefault(); - var existingRevitConnector = convertedElement? - .GetConnectorSet() + var existingRevitConnector = convertedElement + ?.GetConnectorSet() .Where(c => c.Origin.DistanceTo(origin) < .01) .FirstOrDefault(); yield return (connectorAppId, convertedElement, existingRevitConnector); } } - + public void CreateSystemConnections( IEnumerable revitMEPConnectors, Element revitEl, - IConvertedObjectsCache receivedObjectsCache) + IConvertedObjectsCache receivedObjectsCache + ) { foreach (var speckleConnector in revitMEPConnectors) { @@ -1045,14 +1005,18 @@ public void CreateSystemConnections( .Where(c => c.Origin.DistanceTo(origin) < .01) .FirstOrDefault(); - if (newRevitConnector == null) continue; + if (newRevitConnector == null) + continue; - foreach (var (elementAppId, element, existingConnector) in GetRevitConnectorsThatConnectToSpeckleConnector( - speckleConnector, - receivedObjectsCache)) + foreach ( + var (elementAppId, element, existingConnector) in GetRevitConnectorsThatConnectToSpeckleConnector( + speckleConnector, + receivedObjectsCache + ) + ) { existingConnector?.ConnectTo(newRevitConnector); - } + } } } @@ -1187,6 +1151,7 @@ public static RenderMaterial GetMEPSystemMaterial(Element e) DB.Material material = GetMEPSystemRevitMaterial(e); return material != null ? RenderMaterialToSpeckle(material) : null; } + /// /// Retrieves the revit material from assigned system type for mep elements /// @@ -1233,6 +1198,7 @@ public static DB.Material GetMEPSystemRevitMaterial(Element e) return null; } + private static bool IsSupportedMEPCategory(Element e) { var categories = e.Document.Settings.Categories; diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/ConverterRevit.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/ConverterRevit.cs index 0f0bd5320d..3797453e11 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/ConverterRevit.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/ConverterRevit.cs @@ -11,6 +11,7 @@ using RevitSharedResources.Models; using Speckle.Core.Kits; using Speckle.Core.Models; +using Speckle.Core.Models.Extensions; using BE = Objects.BuiltElements; using BER = Objects.BuiltElements.Revit; using BERC = Objects.BuiltElements.Revit.Curve; @@ -67,7 +68,7 @@ public partial class ConverterRevit : ISpeckleConverter /// /// Keeps track of the current host element that is creating any sub-objects it may have. /// - public Element CurrentHostElement { get; set; } + public Element CurrentHostElement => RevitConverterState.Peek?.CurrentHostElement; /// /// Used when sending; keeps track of all the converted objects so far. Child elements first check in here if they should convert themselves again (they may have been converted as part of a parent's hosted elements). @@ -185,6 +186,7 @@ public Base ConvertToSpeckle(object @object) Base returnObject = null; List notes = new List(); string id = @object is Element element ? element.UniqueId : string.Empty; + switch (@object) { case DB.Document o: @@ -485,25 +487,12 @@ public object ConvertToNativeObject(Base @object) // Get settings for receive direct meshes , assumes objects aren't nested like in Tekla Structures Settings.TryGetValue("recieve-objects-mesh", out string recieveModelMesh); - if (bool.Parse(recieveModelMesh ?? "false") == true) - { - try - { - List displayValues = new List { }; - var meshes = @object.GetType().GetProperty("displayValue").GetValue(@object) as List; - //dynamic property = propInfo; - //List meshes = (List)property; - var cat = GetObjectCategory(@object); - var speckleCat = Categories.GetSchemaBuilderCategoryFromBuiltIn(cat.ToString()); - return TryDirectShapeToNative( - new ApplicationObject(@object.id, @object.speckle_type), - meshes, - ToNativeMeshSetting, - speckleCat - ); - } - catch { } - } + if (bool.Parse(recieveModelMesh ?? "false")) + if ((@object is Other.Instance || @object.IsDisplayableObject()) && @object is not BE.Room) + return DisplayableObjectToNative(@object); + else + return null; + //Family Document if (Doc.IsFamilyDocument) { @@ -704,9 +693,9 @@ public object ConvertToNativeObject(Base @object) case Other.BlockInstance o: return BlockInstanceToNative(o); - //hacky but the current comments camera is not a Base object - //used only from DUI and not for normal geometry conversion case Base b: + //hacky but the current comments camera is not a Base object + //used only from DUI and not for normal geometry conversion var boo = b["isHackySpeckleCamera"] as bool?; if (boo == true) return ViewOrientation3DToNative(b); @@ -717,9 +706,9 @@ public object ConvertToNativeObject(Base @object) } } - public object ConvertToNativeDisplayable(Base @object) + public object ConvertToNativeDisplayable(Base @base) { - throw new NotImplementedException(); + return DisplayableObjectToNative(@base); } public List ConvertToSpeckle(List objects) => objects.Select(ConvertToSpeckle).ToList(); @@ -800,7 +789,7 @@ public bool CanConvertToNative(Base @object) if (schema != null) return CanConvertToNative(schema); - return @object switch + var objRes = @object switch { //geometry ICurve _ => true, @@ -852,11 +841,20 @@ public bool CanConvertToNative(Base @object) Organization.DataTable _ => true, _ => false, }; + if (objRes) + return true; + + return false; } public bool CanConvertToNativeDisplayable(Base @object) { - return false; + // check for schema + var schema = @object["@SpeckleSchema"] as Base; // check for contained schema + if (schema != null) + return CanConvertToNativeDisplayable(schema); + + return @object.IsDisplayableObject(); } } } diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/ConverterRevitShared.projitems b/Objects/Converters/ConverterRevit/ConverterRevitShared/ConverterRevitShared.projitems index 234a2136b3..cd64593d6e 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/ConverterRevitShared.projitems +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/ConverterRevitShared.projitems @@ -29,6 +29,7 @@ + diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertBeam.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertBeam.cs index 7068187359..5804157d99 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertBeam.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertBeam.cs @@ -113,7 +113,7 @@ public ApplicationObject BeamToNative(Beam speckleBeam, StructuralType structura // TODO: get sub families, it's a family! var state = isUpdate ? ApplicationObject.State.Updated : ApplicationObject.State.Created; appObj.Update(status: state, createdId: revitBeam.UniqueId, convertedItem: revitBeam); - appObj = SetHostedElements(speckleBeam, revitBeam, appObj); + //appObj = SetHostedElements(speckleBeam, revitBeam, appObj); return appObj; } diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertCeiling.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertCeiling.cs index 78756738ab..32fbbde875 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertCeiling.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertCeiling.cs @@ -103,7 +103,7 @@ public ApplicationObject CeilingToNative(Ceiling speckleCeiling) SetInstanceParameters(revitCeiling, speckleCeiling); appObj.Update(status: ApplicationObject.State.Created, createdId: revitCeiling.UniqueId, convertedItem: revitCeiling); - appObj = SetHostedElements(speckleCeiling, revitCeiling, appObj); + //appObj = SetHostedElements(speckleCeiling, revitCeiling, appObj); return appObj; } #endif diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertColumn.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertColumn.cs index 75a8f26627..e204ca6767 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertColumn.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertColumn.cs @@ -159,7 +159,7 @@ public ApplicationObject ColumnToNative(Column speckleColumn) var state = isUpdate ? ApplicationObject.State.Updated : ApplicationObject.State.Created; appObj.Update(status: state, createdId: revitColumn.UniqueId, convertedItem: revitColumn); // TODO: nested elements. - appObj = SetHostedElements(speckleColumn, revitColumn, appObj); + //appObj = SetHostedElements(speckleColumn, revitColumn, appObj); return appObj; } diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertDirectShape.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertDirectShape.cs index 1ca63475dd..0734415ad7 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertDirectShape.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertDirectShape.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Autodesk.Revit.DB; @@ -156,13 +156,21 @@ public ApplicationObject DirectShapeToNative(DirectShape speckleDs, ToNativeMesh BuiltInCategory.TryParse(bicName, out bic); var cat = Doc.Settings.Categories.get_Item(bic); - var revitDs = DB.DirectShape.CreateElement(Doc, cat.Id); - revitDs.ApplicationId = speckleDs.applicationId; - revitDs.ApplicationDataId = Guid.NewGuid().ToString(); - revitDs.SetShape(converted); - revitDs.Name = speckleDs.name; - SetInstanceParameters(revitDs, speckleDs); - appObj.Update(status: ApplicationObject.State.Created, createdId: revitDs.UniqueId, convertedItem: revitDs); + try + { + var revitDs = DB.DirectShape.CreateElement(Doc, cat.Id); + if (speckleDs.applicationId != null) + revitDs.ApplicationId = speckleDs.applicationId; + revitDs.ApplicationDataId = Guid.NewGuid().ToString(); + revitDs.SetShape(converted); + revitDs.Name = speckleDs.name; + SetInstanceParameters(revitDs, speckleDs); + appObj.Update(status: ApplicationObject.State.Created, createdId: revitDs.UniqueId, convertedItem: revitDs); + } + catch (Exception ex) + { + appObj.Update(status: ApplicationObject.State.Failed, logItem: $"{ex.Message}"); + } return appObj; } diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertDisplayableObject.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertDisplayableObject.cs new file mode 100644 index 0000000000..e708aa1faa --- /dev/null +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertDisplayableObject.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Autodesk.Revit.DB; +using Objects.BuiltElements.Revit; +using Speckle.Core.Models; +using Speckle.Core.Models.Extensions; +using BE = Objects.BuiltElements; +using DirectShape = Objects.BuiltElements.Revit.DirectShape; +using Parameter = Objects.BuiltElements.Revit.Parameter; + +namespace Objects.Converter.Revit; + +public partial class ConverterRevit +{ + /// + /// Converts a displayable object into a DirectShape. + /// + /// The "displayable object". This can be any object that has a `displayValue` property, and an optional `name` property. + /// The application object containing the conversion result and info. + public ApplicationObject DisplayableObjectToNative(Base obj) + { + if (obj.IsDisplayableObject()) + { + // Extract info from the object dynamically. + var name = obj.TryGetName() ?? "speckleDisplayableObject" + obj.id; + var displayValue = obj.TryGetDisplayValue() ?? throw new Exception("Display value was empty or null"); + + var parameters = obj.TryGetParameters(); + var category = GetSpeckleObjectCategory(obj); + + // Create a temp DirectShape and use the DirectShape conversion routine + var ds = new DirectShape(name, category, displayValue.ToList(), parameters?.ToList()); + return DirectShapeToNative(ds, ToNativeMeshSettingEnum.Default); + } + else if (obj is Other.Instance instance) + { + // Extract info from the object dynamically. + var name = obj.TryGetName() ?? "speckleDisplayableObject" + obj.id; + var displayValue = instance.GetTransformedGeometry().Cast(); + + var parameters = obj.TryGetParameters(); + var category = GetSpeckleObjectCategory(obj); + + // Create a temp DirectShape and use the DirectShape conversion routine + var ds = new DirectShape(name, category, displayValue.ToList(), parameters?.ToList()); + return DirectShapeToNative(ds, ToNativeMeshSettingEnum.Default); + } + else + { + throw new Exception("Object is not displayable (is not an instance, has no display value or it was null"); + } + } + + public RevitCategory GetSpeckleObjectCategory(Base @object) + { + switch (@object) + { + case BE.Beam _: + case BE.Brace _: + case BE.TeklaStructures.TeklaContourPlate _: + return RevitCategory.StructuralFraming; + case BE.TeklaStructures.Bolts _: + return RevitCategory.StructConnectionBolts; + case BE.TeklaStructures.Welds _: + return RevitCategory.StructConnectionWelds; + case BE.Floor _: + return RevitCategory.Floors; + case BE.Ceiling _: + return RevitCategory.Ceilings; + case BE.Column _: + return RevitCategory.Columns; + case BE.Pipe _: + return RevitCategory.PipeSegments; + case BE.Rebar _: + return RevitCategory.Rebar; + case BE.Topography _: + return RevitCategory.Topography; + case BE.Wall _: + return RevitCategory.Walls; + case BE.Roof _: + return RevitCategory.Roofs; + case BE.Duct _: + return RevitCategory.DuctSystem; + case BE.CableTray _: + return RevitCategory.CableTray; + default: + return RevitCategory.GenericModel; + } + } +} diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertFaceWall.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertFaceWall.cs index 21461152f1..dd1a0a8836 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertFaceWall.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertFaceWall.cs @@ -101,7 +101,7 @@ public ApplicationObject FaceWallToNative(RevitFaceWall speckleWall) SetInstanceParameters(revitWall, speckleWall); appObj.Update(status: ApplicationObject.State.Created, createdId: revitWall.UniqueId, convertedItem: revitWall); - appObj = SetHostedElements(speckleWall, revitWall, appObj); + //appObj = SetHostedElements(speckleWall, revitWall, appObj); return appObj; } public ApplicationObject FaceWallToNativeV2(RevitFaceWall speckleWall) @@ -150,7 +150,7 @@ public ApplicationObject FaceWallToNativeV2(RevitFaceWall speckleWall) //Doc.Delete(freeform.Id); SetInstanceParameters(revitWall, speckleWall); appObj.Update(status: ApplicationObject.State.Created, createdId: revitWall.UniqueId, convertedItem: revitWall); - appObj = SetHostedElements(speckleWall, revitWall, appObj); + //appObj = SetHostedElements(speckleWall, revitWall, appObj); return appObj; } catch (Exception e) diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertFamilyInstance.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertFamilyInstance.cs index 6d5798531f..8e4fa983b7 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertFamilyInstance.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertFamilyInstance.cs @@ -723,7 +723,7 @@ out FamilyPlacementType placementType SetInstanceParameters(familyInstance, instance); var state = isUpdate ? ApplicationObject.State.Updated : ApplicationObject.State.Created; appObj.Update(status: state, createdId: familyInstance.UniqueId, convertedItem: familyInstance); - appObj = SetHostedElements(instance, familyInstance, appObj); + //appObj = SetHostedElements(instance, familyInstance, appObj); return appObj; } diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertFloor.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertFloor.cs index d195e3321a..5266748744 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertFloor.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertFloor.cs @@ -130,7 +130,7 @@ out ApplicationObject.State state SetInstanceParameters(revitFloor, speckleFloor); appObj.Update(status: ApplicationObject.State.Created, createdId: revitFloor.UniqueId, convertedItem: revitFloor); - appObj = SetHostedElements(speckleFloor, revitFloor, appObj); + //appObj = SetHostedElements(speckleFloor, revitFloor, appObj); return appObj; } diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertProfileWall.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertProfileWall.cs index 7383cf88f8..6ccf93951e 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertProfileWall.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertProfileWall.cs @@ -69,7 +69,7 @@ public ApplicationObject ProfileWallToNative(RevitProfileWall speckleRevitWall) SetInstanceParameters(revitWall, speckleRevitWall); appObj.Update(status: ApplicationObject.State.Created, createdId: revitWall.UniqueId, convertedItem: revitWall); - appObj = SetHostedElements(speckleRevitWall, revitWall, appObj); + //appObj = SetHostedElements(speckleRevitWall, revitWall, appObj); return appObj; } } diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertRoof.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertRoof.cs index 628db56c5b..ac5ea3bb82 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertRoof.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertRoof.cs @@ -170,7 +170,7 @@ public ApplicationObject RoofToNative(Roof speckleRoof) appObj.Update(status: ApplicationObject.State.Created, createdId: revitRoof.UniqueId, convertedItem: revitRoof); Doc.Regenerate(); - appObj = SetHostedElements(speckleRoof, revitRoof, appObj); + //appObj = SetHostedElements(speckleRoof, revitRoof, appObj); return appObj; } diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertView.Schedule.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertView.Schedule.cs index 659f20a6ce..0f629cc336 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertView.Schedule.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertView.Schedule.cs @@ -17,9 +17,9 @@ public partial class ConverterRevit private ApplicationObject DataTableToNative(DataTable speckleTable) { var docObj = GetExistingElementByApplicationId(speckleTable.applicationId); - var appObj = new ApplicationObject(speckleTable.id, speckleTable.speckle_type) - { - applicationId = speckleTable.applicationId + var appObj = new ApplicationObject(speckleTable.id, speckleTable.speckle_type) + { + applicationId = speckleTable.applicationId }; if (docObj == null) @@ -29,7 +29,9 @@ private ApplicationObject DataTableToNative(DataTable speckleTable) if (docObj is not ViewSchedule revitSchedule) { - throw new Exception($"Existing element with UniqueId = {docObj.UniqueId} is of the type {docObj.GetType()}, not of the expected type, DB.ViewSchedule"); + throw new Exception( + $"Existing element with UniqueId = {docObj.UniqueId} is of the type {docObj.GetType()}, not of the expected type, DB.ViewSchedule" + ); } var speckleIndexToRevitParameterDataMap = new Dictionary(); @@ -38,8 +40,7 @@ private ApplicationObject DataTableToNative(DataTable speckleTable) AddToIndexToScheduleMap(columnInfo, speckleTable, speckleIndexToRevitParameterDataMap); } - var originalTableIds = new FilteredElementCollector(Doc, revitSchedule.Id) - .ToElementIds(); + var originalTableIds = new FilteredElementCollector(Doc, revitSchedule.Id).ToElementIds(); foreach (var rowInfo in RevitScheduleUtils.ScheduleRowIteration(revitSchedule)) { UpdateDataInRow(rowInfo, originalTableIds, revitSchedule, speckleTable, speckleIndexToRevitParameterDataMap); @@ -49,7 +50,11 @@ private ApplicationObject DataTableToNative(DataTable speckleTable) return appObj; } - private static void AddToIndexToScheduleMap(ScheduleColumnIterationInfo info, DataTable speckleTable, Dictionary speckleIndexToRevitParameterDataMap) + private static void AddToIndexToScheduleMap( + ScheduleColumnIterationInfo info, + DataTable speckleTable, + Dictionary speckleIndexToRevitParameterDataMap + ) { var fieldInt = info.field.ParameterId.IntegerValue; @@ -66,8 +71,14 @@ private static void AddToIndexToScheduleMap(ScheduleColumnIterationInfo info, Da paramId = paramIdLong; } - if (paramId == null) { continue; } - if (paramId != fieldInt) { continue; } + if (paramId == null) + { + continue; + } + if (paramId != fieldInt) + { + continue; + } incomingColumnIndex = i; break; @@ -92,14 +103,31 @@ private static void AddToIndexToScheduleMap(ScheduleColumnIterationInfo info, Da speckleIndexToRevitParameterDataMap.Add(incomingColumnIndex, scheduleData); } - private void UpdateDataInRow(ScheduleRowIterationInfo info, ICollection originalTableIds, ViewSchedule revitSchedule, DataTable speckleTable, Dictionary speckleIndexToRevitParameterDataMap) + private void UpdateDataInRow( + ScheduleRowIterationInfo info, + ICollection originalTableIds, + ViewSchedule revitSchedule, + DataTable speckleTable, + Dictionary speckleIndexToRevitParameterDataMap + ) { - var elementIds = ElementApplicationIdsInRow(info.rowIndex, info.section, originalTableIds, revitSchedule, info.tableSection).ToList(); + var elementIds = ElementApplicationIdsInRow( + info.rowIndex, + info.section, + originalTableIds, + revitSchedule, + info.tableSection + ) + .ToList(); - if (elementIds.Count == 0) { return; } + if (elementIds.Count == 0) + { + return; + } - var speckleObjectRowIndex = speckleTable.rowMetadata - .FindIndex(b => b["RevitApplicationIds"] is IList list && list.Contains(elementIds.First())); + var speckleObjectRowIndex = speckleTable.rowMetadata.FindIndex( + b => b["RevitApplicationIds"] is IList list && list.Contains(elementIds.First()) + ); foreach (var kvp in speckleIndexToRevitParameterDataMap) { @@ -124,7 +152,10 @@ private void UpdateDataInRow(ScheduleRowIterationInfo info, ICollection>(); var columnHeaders = new List(); @@ -170,7 +197,7 @@ private DataTable ScheduleToSpeckle(DB.ViewSchedule revitSchedule) AddHeaderRow(speckleTable, columnHeaders); } - return speckleTable; + return speckleTable; } private void AddHeaderRow(DataTable speckleTable, List headers) @@ -178,7 +205,12 @@ private void AddHeaderRow(DataTable speckleTable, List headers) speckleTable.AddRow(metadata: new Base(), index: speckleTable.headerRowIndex, headers.ToArray()); } - private void DefineColumnMetadata(ViewSchedule revitSchedule, DataTable speckleTable, ICollection originalTableIds, List columnHeaders) + private void DefineColumnMetadata( + ViewSchedule revitSchedule, + DataTable speckleTable, + ICollection originalTableIds, + List columnHeaders + ) { Element firstElement = null; Element firstType = null; @@ -194,7 +226,14 @@ private void DefineColumnMetadata(ViewSchedule revitSchedule, DataTable speckleT } } - private static void AddColumnMetadataToDataTable(ScheduleColumnIterationInfo info, ViewSchedule revitSchedule, DataTable speckleTable, List columnHeaders, Element firstType, Element firstElement) + private static void AddColumnMetadataToDataTable( + ScheduleColumnIterationInfo info, + ViewSchedule revitSchedule, + DataTable speckleTable, + List columnHeaders, + Element firstType, + Element firstElement + ) { // add column header to list for potential future use columnHeaders.Add(info.field.ColumnHeading); @@ -225,15 +264,23 @@ private static void AddColumnMetadataToDataTable(ScheduleColumnIterationInfo inf else { var scheduleCategory = (BuiltInCategory)revitSchedule.Definition.CategoryId.IntegerValue; - SpeckleLog.Logger.Warning("Schedule of category, {scheduleCategory}, contains field of type {builtInParameter} which has an unsupported field type, {fieldType}", + SpeckleLog.Logger.Warning( + "Schedule of category, {scheduleCategory}, contains field of type {builtInParameter} which has an unsupported field type, {fieldType}", scheduleCategory, builtInParameter, - info.field.FieldType.ToString()); + info.field.FieldType.ToString() + ); } speckleTable.DefineColumn(columnMetadata); } - private void PopulateDataTableRows(ViewSchedule revitSchedule, DataTable speckleTable, ICollection originalTableIds, int[] columnHeaderIndexArray, List columnHeaders) + private void PopulateDataTableRows( + ViewSchedule revitSchedule, + DataTable speckleTable, + ICollection originalTableIds, + int[] columnHeaderIndexArray, + List columnHeaders + ) { var minHeaderIndex = columnHeaderIndexArray.Min(); var maxHeaderIndex = columnHeaderIndexArray.Max(); @@ -282,17 +329,23 @@ private int[] GetTableHeaderIndexArray(ViewSchedule revitSchedule, List { if (!revitSchedule.Definition.ShowHeaders) { - return TransactionManager.ExecuteInTemporaryTransaction(() => - { - revitSchedule.Definition.ShowHeaders = true; - return GetHeaderIndexArrayFromScheduleWithHeaders(revitSchedule, columnHeaders); - }, revitSchedule.Document); + return TransactionManager.ExecuteInTemporaryTransaction( + () => + { + revitSchedule.Definition.ShowHeaders = true; + return GetHeaderIndexArrayFromScheduleWithHeaders(revitSchedule, columnHeaders); + }, + revitSchedule.Document + ); } return GetHeaderIndexArrayFromScheduleWithHeaders(revitSchedule, columnHeaders); } - private static int[] GetHeaderIndexArrayFromScheduleWithHeaders(ViewSchedule revitSchedule, List columnHeaders) + private static int[] GetHeaderIndexArrayFromScheduleWithHeaders( + ViewSchedule revitSchedule, + List columnHeaders + ) { string nextCellValue = null; var headerIndexArray = new int[columnHeaders.Count]; @@ -329,14 +382,29 @@ private static int[] GetHeaderIndexArrayFromScheduleWithHeaders(ViewSchedule rev return Enumerable.Repeat(0, columnHeaders.Count).ToArray(); } - private bool AddRowToSpeckleTable(ViewSchedule revitSchedule, DataTable speckleTable, ICollection originalTableIds, SectionType tableSection, TableSectionData section, List rowValues, int rowIndex) + private bool AddRowToSpeckleTable( + ViewSchedule revitSchedule, + DataTable speckleTable, + ICollection originalTableIds, + SectionType tableSection, + TableSectionData section, + List rowValues, + int rowIndex + ) { if (!rowValues.Where(s => !string.IsNullOrEmpty(s)).Any()) { return false; } var metadata = new Base(); - metadata["RevitApplicationIds"] = ElementApplicationIdsInRow(rowIndex, section, originalTableIds, revitSchedule, tableSection).ToList(); + metadata["RevitApplicationIds"] = ElementApplicationIdsInRow( + rowIndex, + section, + originalTableIds, + revitSchedule, + tableSection + ) + .ToList(); try { @@ -351,7 +419,12 @@ private bool AddRowToSpeckleTable(ViewSchedule revitSchedule, DataTable speckleT return true; } - private static List GetRowValues(ViewSchedule revitSchedule, SectionType tableSection, int columnCount, int rowIndex) + private static List GetRowValues( + ViewSchedule revitSchedule, + SectionType tableSection, + int columnCount, + int rowIndex + ) { var rowData = new List(); for (var columnIndex = 0; columnIndex < columnCount; columnIndex++) @@ -364,19 +437,21 @@ private static List GetRowValues(ViewSchedule revitSchedule, SectionType #endregion public static IEnumerable ElementApplicationIdsInRow( - int rowNumber, - TableSectionData section, - ICollection orginialTableIds, - DB.ViewSchedule revitSchedule, - SectionType tableSection) + int rowNumber, + TableSectionData section, + ICollection orginialTableIds, + DB.ViewSchedule revitSchedule, + SectionType tableSection + ) { - var remainingIdsInRow = TransactionManager.ExecuteInTemporaryTransaction(() => - { - section.RemoveRow(rowNumber); - return new FilteredElementCollector(revitSchedule.Document, revitSchedule.Id) - .ToElementIds() - .ToList(); - }, revitSchedule.Document); + var remainingIdsInRow = TransactionManager.ExecuteInTemporaryTransaction( + () => + { + section.RemoveRow(rowNumber); + return new FilteredElementCollector(revitSchedule.Document, revitSchedule.Id).ToElementIds().ToList(); + }, + revitSchedule.Document + ); // the section must be recomputed here because of our hacky row deleting trick var table = revitSchedule.GetTableData(); @@ -389,7 +464,8 @@ public static IEnumerable ElementApplicationIdsInRow( foreach (var id in orginialTableIds) { - if (remainingIdsInRow.Contains(id)) continue; + if (remainingIdsInRow.Contains(id)) + continue; yield return revitSchedule.Document.GetElement(id).UniqueId; } } @@ -401,6 +477,7 @@ public struct RevitParameterData public BuiltInParameter Parameter; public bool IsTypeParam; } + public struct ScheduleRowIterationInfo { public SectionType tableSection; @@ -409,6 +486,7 @@ public struct ScheduleRowIterationInfo public int columnCount; public int masterRowIndex; } + public struct ScheduleColumnIterationInfo { public ScheduleField field; @@ -416,9 +494,13 @@ public struct ScheduleColumnIterationInfo public int columnCount; public int numHiddenFields; } + public static class RevitScheduleUtils { - public static IEnumerable ScheduleRowIteration(ViewSchedule revitSchedule, Dictionary> skippedIndicies = null) + public static IEnumerable ScheduleRowIteration( + ViewSchedule revitSchedule, + Dictionary> skippedIndicies = null + ) { var masterRowIndex = 0; @@ -437,16 +519,20 @@ public static IEnumerable ScheduleRowIteration(ViewSch for (var rowIndex = 0; rowIndex < rowCount; rowIndex++) { - yield return new ScheduleRowIterationInfo + yield return new ScheduleRowIterationInfo { tableSection = tableSection, section = section, - rowIndex = rowIndex, + rowIndex = rowIndex, columnCount = columnCount, masterRowIndex = masterRowIndex }; - if (skippedIndicies == null || !skippedIndicies.TryGetValue(tableSection, out var indicies) || !indicies.Contains(rowIndex)) + if ( + skippedIndicies == null + || !skippedIndicies.TryGetValue(tableSection, out var indicies) + || !indicies.Contains(rowIndex) + ) { // this "skippedIndicies" dict contains the indicies that contain only empty values // these values were skipped when adding them to the DataTable, so the indicies of the revitSchedule @@ -458,6 +544,7 @@ public static IEnumerable ScheduleRowIteration(ViewSch } } } + public static IEnumerable ScheduleColumnIteration(ViewSchedule revitSchedule) { var scheduleFieldOrder = revitSchedule.Definition.GetFieldOrder(); diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertWall.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertWall.cs index 35b08f9d8c..f1b903657b 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertWall.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertWall.cs @@ -21,7 +21,10 @@ public partial class ConverterRevit public ApplicationObject WallToNative(BuiltElements.Wall speckleWall) { var revitWall = GetExistingElementByApplicationId(speckleWall.applicationId) as DB.Wall; - var appObj = new ApplicationObject(speckleWall.id, speckleWall.speckle_type) { applicationId = speckleWall.applicationId }; + var appObj = new ApplicationObject(speckleWall.id, speckleWall.speckle_type) + { + applicationId = speckleWall.applicationId + }; // skip if element already exists in doc & receive mode is set to ignore if (IsIgnore(revitWall, appObj)) @@ -113,7 +116,6 @@ public ApplicationObject WallToNative(BuiltElements.Wall speckleWall) TrySetParam(revitWall, BuiltInParameter.WALL_BASE_CONSTRAINT, level); - // now that we've moved the wall, rejoin the wall ends if (!joinSettings.Contains(StructuralWalls) && structural) { @@ -144,7 +146,6 @@ public ApplicationObject WallToNative(BuiltElements.Wall speckleWall) TrySetParam(revitWall, BuiltInParameter.WALL_BASE_OFFSET, spklRevitWall.baseOffset, speckleWall.units); TrySetParam(revitWall, BuiltInParameter.WALL_TOP_OFFSET, spklRevitWall.topOffset, speckleWall.units); - } else // Set wall unconnected height. TrySetParam(revitWall, BuiltInParameter.WALL_USER_HEIGHT_PARAM, speckleWall.height, speckleWall.units); @@ -155,7 +156,7 @@ public ApplicationObject WallToNative(BuiltElements.Wall speckleWall) appObj.Update(status: state, createdId: revitWall.UniqueId, convertedItem: revitWall); SetWallVoids(revitWall, speckleWall); - appObj = SetHostedElements(speckleWall, revitWall, appObj); + //appObj = SetHostedElements(speckleWall, revitWall, appObj); return appObj; } @@ -190,34 +191,43 @@ public Base WallToSpeckle(DB.Wall revitWall, out List notes) speckleWall.elements.Add(WallToSpeckle(wall, out List stackedWallNotes)); } - speckleWall.displayValue = GetElementDisplayValue(revitWall, - new Options() { DetailLevel = ViewDetailLevel.Fine, ComputeReferences = false }); + speckleWall.displayValue = GetElementDisplayValue( + revitWall, + new Options() { DetailLevel = ViewDetailLevel.Fine, ComputeReferences = false } + ); } else { AddHostedDependentElements( - revitWall, + revitWall, speckleWall, - GetWallSubElementsInView(BuiltInCategory.OST_CurtainWallMullions) ?? grid.GetMullionIds().ToList()); + GetWallSubElementsInView(BuiltInCategory.OST_CurtainWallMullions) ?? grid.GetMullionIds().ToList() + ); AddHostedDependentElements( - revitWall, + revitWall, speckleWall, - GetWallSubElementsInView(BuiltInCategory.OST_CurtainWallPanels) ?? grid.GetPanelIds().ToList()); + GetWallSubElementsInView(BuiltInCategory.OST_CurtainWallPanels) ?? grid.GetPanelIds().ToList() + ); } - GetAllRevitParamsAndIds(speckleWall, revitWall, new List - { - "WALL_USER_HEIGHT_PARAM", - "WALL_BASE_OFFSET", - "WALL_TOP_OFFSET", - "WALL_BASE_CONSTRAINT", - "WALL_HEIGHT_TYPE", - "WALL_STRUCTURAL_SIGNIFICANT" - }); + GetAllRevitParamsAndIds( + speckleWall, + revitWall, + new List + { + "WALL_USER_HEIGHT_PARAM", + "WALL_BASE_OFFSET", + "WALL_TOP_OFFSET", + "WALL_BASE_CONSTRAINT", + "WALL_HEIGHT_TYPE", + "WALL_STRUCTURAL_SIGNIFICANT" + } + ); GetWallVoids(speckleWall, revitWall); GetHostedElements(speckleWall, revitWall, out List hostedNotes); - if (hostedNotes.Any()) notes.AddRange(hostedNotes); + if (hostedNotes.Any()) + notes.AddRange(hostedNotes); return speckleWall; } @@ -231,11 +241,7 @@ public Base WallToSpeckle(DB.Wall revitWall, out List notes) using var filter = new ElementCategoryFilter(category); using var collector = new FilteredElementCollector(Doc, ViewSpecificOptions.View.Id); - return collector - .WhereElementIsNotElementType() - .WherePasses(filter) - .ToElementIds() - .ToList(); + return collector.WhereElementIsNotElementType().WherePasses(filter).ToElementIds().ToList(); } //this is to prevent duplicated panels & mullions from being sent in curtain walls @@ -262,6 +268,7 @@ private void GetWallVoids(Base speckleElement, Wall wall) speckleElement["voids"] = voidsList; #endif } + private void SetWallVoids(Wall wall, Base speckleElement) { #if !REVIT2020 && !REVIT2021 @@ -275,7 +282,7 @@ private void SetWallVoids(Wall wall, Base speckleElement) } else { - // TODO: actually update the profile in order to keep the user's dimensions + // TODO: actually update the profile in order to keep the user's dimensions wall.RemoveProfileSketch(); wall.CreateProfileSketch(); } @@ -291,7 +298,6 @@ private void SetWallVoids(Wall wall, Base speckleElement) foreach (var obj in voidCurves) { - if (!(obj is ICurve @void)) continue; @@ -321,8 +327,7 @@ private void SetWallVoids(Wall wall, Base speckleElement) public class FailuresPreprocessor : IFailuresPreprocessor { - public FailureProcessingResult PreprocessFailures( - FailuresAccessor failuresAccessor) + public FailureProcessingResult PreprocessFailures(FailuresAccessor failuresAccessor) { return FailureProcessingResult.Continue; } diff --git a/Objects/Converters/ConverterRevit/ConverterRevitTests/ConverterRevitTestsShared/SpeckleUtils.cs b/Objects/Converters/ConverterRevit/ConverterRevitTests/ConverterRevitTestsShared/SpeckleUtils.cs index 78c7fe2e18..e9aa16f043 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitTests/ConverterRevitTestsShared/SpeckleUtils.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitTests/ConverterRevitTestsShared/SpeckleUtils.cs @@ -15,8 +15,15 @@ namespace ConverterRevitTests { internal static class SpeckleUtils { - public static SemaphoreSlim Throttler = new SemaphoreSlim(1,1); - internal async static Task RunInTransaction(Action action, DB.Document doc, ConverterRevit converter = null, string transactionName = "transaction", bool ignoreWarnings = false) + public static SemaphoreSlim Throttler = new SemaphoreSlim(1, 1); + + internal async static Task RunInTransaction( + Action action, + DB.Document doc, + ConverterRevit converter = null, + string transactionName = "transaction", + bool ignoreWarnings = false + ) { var tcs = new TaskCompletionSource(); @@ -78,10 +85,14 @@ internal static void DeleteElement(object obj) case DB.Element o: try { - xru.RunInTransaction(() => - { - o.Document.Delete(o.Id); - }, o.Document).Wait(); + xru.RunInTransaction( + () => + { + o.Document.Delete(o.Id); + }, + o.Document + ) + .Wait(); } // element already deleted, don't worry about it catch { } @@ -93,7 +104,8 @@ internal static void DeleteElement(object obj) internal static int GetSpeckleObjectTestNumber(DB.Element element) { - var param = element.Parameters.Cast() + var param = element.Parameters + .Cast() .Where(el => el.Definition.Name == "SpeckleObjectTestNumber") .FirstOrDefault(); @@ -105,15 +117,16 @@ internal static int GetSpeckleObjectTestNumber(DB.Element element) return param.AsInteger(); } + internal static void CustomAssertions(DB.Element element, Base @base) { - var parameters = element.Parameters.Cast() - .Where(el => el.Definition.Name.StartsWith("ToSpeckle")); + var parameters = element.Parameters.Cast().Where(el => el.Definition.Name.StartsWith("ToSpeckle")); foreach (var param in parameters) { var parts = param.Definition.Name.Split('-'); - if (parts.Length != 3) continue; + if (parts.Length != 3) + continue; var assertionType = parts[1]; var prop = parts[2]; diff --git a/Objects/Objects/Geometry/Mesh.cs b/Objects/Objects/Geometry/Mesh.cs index 660bfc2225..17e823e4cb 100644 --- a/Objects/Objects/Geometry/Mesh.cs +++ b/Objects/Objects/Geometry/Mesh.cs @@ -82,9 +82,13 @@ public Mesh( public bool Transform(Transform transform) { // transform vertices - vertices = GetPoints().SelectMany(vertex => { - vertex.TransformTo(transform, out Point transformedVertex); return transformedVertex.ToList(); - }).ToList(); + vertices = GetPoints() + .SelectMany(vertex => + { + vertex.TransformTo(transform, out Point transformedVertex); + return transformedVertex.ToList(); + }) + .ToList(); return true; } @@ -109,6 +113,7 @@ public bool TransformTo(Transform transform, out Mesh mesh) colors = colors, units = units }; + mesh["renderMaterial"] = this["renderMaterial"]; return true; } From a0bf736741bc69f36bcb376d9f9970c24a1bda9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B3zsef=20L=2E=20Kiss?= <50739844+jozseflkiss@users.noreply.github.com> Date: Wed, 23 Aug 2023 15:10:13 +0200 Subject: [PATCH 15/57] feat(Archicad): Add by Category selection method (#2864) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * element type filter * code style * non localized element type names * Equipment is Object subtype --------- Co-authored-by: József L. Kiss <> --- .../Sources/AddOn/Commands/GetDataCommand.cpp | 2 +- .../Sources/AddOn/Commands/GetElementIds.cpp | 31 +++ .../AddOn/Sources/AddOn/FieldNames.hpp | 1 + .../AddOn/Sources/AddOn/ResourceIds.hpp | 11 +- .../AddOn/Sources/AddOn/ResourceStrings.cpp | 6 + .../AddOn/Sources/AddOn/ResourceStrings.hpp | 1 + .../AddOn/Sources/AddOn/Utility.cpp | 182 ++++++++++-------- .../AddOn/Sources/AddOn/Utility.hpp | 3 +- .../Sources/AddOnResources/RFIX/AddOnFix.grc | 78 ++++++++ .../Commands/Command_GetElementIds.cs | 17 +- .../ConnectorArchicad/ConnectorBinding.cs | 28 +++ .../Converters/ElementConverterManager.cs | 21 +- 12 files changed, 289 insertions(+), 92 deletions(-) diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetDataCommand.cpp b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetDataCommand.cpp index 6a1163cc99..8ff0987b34 100644 --- a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetDataCommand.cpp +++ b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetDataCommand.cpp @@ -14,7 +14,7 @@ GS::ErrCode GetDataCommand::ExportClassificationsAndProperties (const API_Elemen { GS::UniString typeName; - err = Utility::GetTypeNameFromElementType(elem.header, typeName); + err = Utility::GetLocalizedElementTypeName (elem.header, typeName); if (err != NoError) return err; diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetElementIds.cpp b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetElementIds.cpp index 22b01cd958..edc86bb282 100644 --- a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetElementIds.cpp +++ b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetElementIds.cpp @@ -49,6 +49,31 @@ static GS::Array GetAllElementGuids () } +static GS::Array GetElementsFilteredByElementTypes (GS::Array& elementTypes) +{ + GS::Array filteredGuids; + GS::Array elementGuids; + GSErrCode err = ACAPI_Element_GetElemList (API_ZombieElemID, &elementGuids, APIFilt_OnVisLayer | APIFilt_In3D); + if (err == NoError) { + for (const API_Guid& guid : elementGuids) { + if (Utility::IsElement3D (guid)) { + API_Elem_Head elementHead = {}; + elementHead.guid = guid; + err = ACAPI_Element_GetHeader (&elementHead); + if (err != NoError) + continue; + + GS::UniString elementTypeName; + Utility::GetNonLocalizedElementTypeName (elementHead, elementTypeName); + if (elementTypes.Contains (elementTypeName)) + filteredGuids.Push (guid); + } + } + } + return filteredGuids; +} + + GS::String GetElementIds::GetName () const { return GetElementIdsCommandName; @@ -67,6 +92,12 @@ GS::ObjectState GetElementIds::Execute (const GS::ObjectState& parameters, GS::P elementGuids = GetSelectedElementGuids (); else if (elementFilter == "All") elementGuids = GetAllElementGuids (); + else if (elementFilter == "ElementType") { + GS::Array elementTypes; + parameters.Get (ElementBase::FilterBy, elementTypes); + if(elementTypes.GetSize() > 0) + elementGuids = GetElementsFilteredByElementTypes (elementTypes); + } const auto& listAdder = retVal.AddList (ElementBase::ApplicationIds); for (const API_Guid& guid : elementGuids) { diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/FieldNames.hpp b/ConnectorArchicad/AddOn/Sources/AddOn/FieldNames.hpp index 5dce0c6bf6..1cb4425c36 100644 --- a/ConnectorArchicad/AddOn/Sources/AddOn/FieldNames.hpp +++ b/ConnectorArchicad/AddOn/Sources/AddOn/FieldNames.hpp @@ -26,6 +26,7 @@ static const char* ApplicationId = "applicationId"; static const char* ApplicationIds = "applicationIds"; static const char* ParentElementId = "parentApplicationId"; static const char* ElementFilter = "elementFilter"; +static const char* FilterBy = "filterBy"; static const char* ElementType = "elementType"; static const char* ElementTypes = "elementTypes"; static const char* Elements = "elements"; diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/ResourceIds.hpp b/ConnectorArchicad/AddOn/Sources/AddOn/ResourceIds.hpp index 8c6f700ede..e01654193d 100644 --- a/ConnectorArchicad/AddOn/Sources/AddOn/ResourceIds.hpp +++ b/ConnectorArchicad/AddOn/Sources/AddOn/ResourceIds.hpp @@ -1,11 +1,12 @@ #ifndef RESOURCEIDS_HPP #define RESOURCEIDS_HPP -#define ID_ADDON_INFO 32000 -#define ID_ADDON_MENU 32500 -#define ID_DEFAULT_STORY_FORMAT 32800 -#define ID_LOG_MESSAGES 33000 -#define ID_ELEMENT_TYPE_STRINGS 34000 +#define ID_ADDON_INFO 32000 +#define ID_ADDON_MENU 32010 +#define ID_DEFAULT_STORY_FORMAT 32020 +#define ID_LOG_MESSAGES 33030 +#define ID_ELEMENT_TYPE_STRINGS 32040 +#define ID_FIX_ELEMENT_TYPE_STRINGS 32050 #define ID_LOG_MESSAGE_LIBPART_SEARCH_ERROR 1 #define ID_LOG_MESSAGE_ATTRIBUTE_SEARCH_ERROR 2 diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/ResourceStrings.cpp b/ConnectorArchicad/AddOn/Sources/AddOn/ResourceStrings.cpp index 4c7af5be08..18b2865c50 100644 --- a/ConnectorArchicad/AddOn/Sources/AddOn/ResourceStrings.cpp +++ b/ConnectorArchicad/AddOn/Sources/AddOn/ResourceStrings.cpp @@ -25,3 +25,9 @@ const GS::UniString& ResourceStrings::GetElementTypeStringFromResource (const El { return TGetStringFromResource (ID_ELEMENT_TYPE_STRINGS, resourceItemId); } + + +const GS::UniString& ResourceStrings::GetFixElementTypeStringFromResource (const ElementTypeStringItems& resourceItemId) +{ + return TGetStringFromResource (ID_FIX_ELEMENT_TYPE_STRINGS, resourceItemId); +} diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/ResourceStrings.hpp b/ConnectorArchicad/AddOn/Sources/AddOn/ResourceStrings.hpp index 232cbde786..1a5abe9b8d 100644 --- a/ConnectorArchicad/AddOn/Sources/AddOn/ResourceStrings.hpp +++ b/ConnectorArchicad/AddOn/Sources/AddOn/ResourceStrings.hpp @@ -84,6 +84,7 @@ enum class ElementTypeStringItems { }; const GS::UniString& GetElementTypeStringFromResource (const ElementTypeStringItems& resourceItemId); +const GS::UniString& GetFixElementTypeStringFromResource (const ElementTypeStringItems& resourceItemId); } diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/Utility.cpp b/ConnectorArchicad/AddOn/Sources/AddOn/Utility.cpp index 61c907c4e3..a8da5b49d8 100644 --- a/ConnectorArchicad/AddOn/Sources/AddOn/Utility.cpp +++ b/ConnectorArchicad/AddOn/Sources/AddOn/Utility.cpp @@ -33,93 +33,115 @@ API_ElemTypeID GetElementType (const API_Guid& guid) } -GS::ErrCode GetTypeNameFromElementType (const API_Elem_Head& header, GS::UniString& typeName) +GSErrCode GetElementTypeStringItem (const API_Elem_Head& header, ResourceStrings::ElementTypeStringItems& elementTypeStringItem) { #ifdef ServerMainVers_2600 - return ACAPI_Goodies_GetElemTypeName (header.type, typeName); + switch (header.type.typeID) { #else - ResourceStrings::ElementTypeStringItems elementTypeStringItems; switch (header.typeID) { - case API_WallID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::WallString; break; - case API_ColumnID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::ColumnString; break; - case API_BeamID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::BeamString; break; - case API_WindowID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::WindowString; break; - case API_DoorID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::DoorString; break; - case API_ObjectID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::ObjectString; break; - case API_LampID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::LampString; break; - case API_SlabID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::SlabString; break; - case API_RoofID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::RoofString; break; - case API_MeshID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::MeshString; break; - case API_DimensionID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::DimensionString; break; - case API_RadialDimensionID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::RadialDimensionString; break; - case API_LevelDimensionID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::LevelDimensionString; break; - case API_AngleDimensionID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::AngleDimensionString; break; - case API_TextID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::TextString; break; - case API_LabelID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::LabelString; break; - case API_ZoneID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::ZoneString; break; - case API_HatchID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::HatchString; break; - case API_LineID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::LineString; break; - case API_PolyLineID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::PolyLineString; break; - case API_ArcID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::ArcString; break; - case API_CircleID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::CircleString; break; - case API_SplineID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::SplineString; break; - case API_HotspotID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::HotspotString; break; - case API_CutPlaneID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::CutPlaneString; break; - case API_CameraID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::CameraString; break; - case API_CamSetID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::CamSetString; break; - case API_GroupID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::GroupString; break; - case API_SectElemID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::SectElemString; break; - case API_DrawingID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::DrawingString; break; - case API_PictureID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::PictureString; break; - case API_DetailID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::DetailString; break; - case API_ElevationID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::ElevationString; break; - case API_InteriorElevationID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::InteriorElevationString; break; - case API_WorksheetID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::WorksheetString; break; - case API_HotlinkID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::HotlinkString; break; - case API_CurtainWallID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::CurtainWallString; break; - case API_CurtainWallSegmentID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::CurtainWallSegmentString; break; - case API_CurtainWallFrameID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::CurtainWallFrameString; break; - case API_CurtainWallPanelID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::CurtainWallPanelString; break; - case API_CurtainWallJunctionID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::CurtainWallJunctionString; break; - case API_CurtainWallAccessoryID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::CurtainWallAccessoryString; break; - case API_ShellID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::ShellString; break; - case API_SkylightID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::SkylightString; break; - case API_MorphID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::MorphString; break; - case API_ChangeMarkerID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::ChangeMarkerString; break; - case API_StairID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::StairString; break; - case API_RiserID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::RiserString; break; - case API_TreadID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::TreadString; break; - case API_StairStructureID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::StairStructureString; break; - case API_RailingID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::RailingString; break; - case API_RailingToprailID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::RailingToprailString; break; - case API_RailingHandrailID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::RailingHandrailString; break; - case API_RailingRailID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::RailingRailString; break; - case API_RailingPostID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::RailingPostString; break; - case API_RailingInnerPostID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::RailingInnerPostString; break; - case API_RailingBalusterID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::RailingBalusterString; break; - case API_RailingPanelID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::RailingPanelString; break; - case API_RailingSegmentID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::RailingSegmentString; break; - case API_RailingNodeID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::RailingNodeString; break; - case API_RailingBalusterSetID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::RailingBalusterSetString; break; - case API_RailingPatternID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::RailingPatternString; break; - case API_RailingToprailEndID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::RailingToprailEndString; break; - case API_RailingHandrailEndID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::RailingHandrailEndString; break; - case API_RailingRailEndID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::RailingRailEndString; break; - case API_RailingToprailConnectionID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::RailingToprailConnectionString; break; - case API_RailingHandrailConnectionID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::RailingHandrailConnectionString; break; - case API_RailingRailConnectionID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::RailingRailConnectionString; break; - case API_RailingEndFinishID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::RailingEndFinishString; break; - case API_AnalyticalSupportID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::AnalyticalSupportString; break; - case API_AnalyticalLinkID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::AnalyticalLinkString; break; - case API_BeamSegmentID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::BeamSegmentString; break; - case API_ColumnSegmentID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::ColumnSegmentString; break; - case API_OpeningID: elementTypeStringItems = ResourceStrings::ElementTypeStringItems::OpeningString; break; +#endif + case API_WallID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::WallString; break; + case API_ColumnID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::ColumnString; break; + case API_BeamID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::BeamString; break; + case API_WindowID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::WindowString; break; + case API_DoorID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::DoorString; break; + case API_ObjectID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::ObjectString; break; + case API_LampID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::LampString; break; + case API_SlabID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::SlabString; break; + case API_RoofID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::RoofString; break; + case API_MeshID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::MeshString; break; + case API_DimensionID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::DimensionString; break; + case API_RadialDimensionID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::RadialDimensionString; break; + case API_LevelDimensionID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::LevelDimensionString; break; + case API_AngleDimensionID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::AngleDimensionString; break; + case API_TextID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::TextString; break; + case API_LabelID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::LabelString; break; + case API_ZoneID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::ZoneString; break; + case API_HatchID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::HatchString; break; + case API_LineID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::LineString; break; + case API_PolyLineID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::PolyLineString; break; + case API_ArcID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::ArcString; break; + case API_CircleID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::CircleString; break; + case API_SplineID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::SplineString; break; + case API_HotspotID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::HotspotString; break; + case API_CutPlaneID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::CutPlaneString; break; + case API_CameraID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::CameraString; break; + case API_CamSetID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::CamSetString; break; + case API_GroupID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::GroupString; break; + case API_SectElemID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::SectElemString; break; + case API_DrawingID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::DrawingString; break; + case API_PictureID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::PictureString; break; + case API_DetailID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::DetailString; break; + case API_ElevationID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::ElevationString; break; + case API_InteriorElevationID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::InteriorElevationString; break; + case API_WorksheetID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::WorksheetString; break; + case API_HotlinkID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::HotlinkString; break; + case API_CurtainWallID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::CurtainWallString; break; + case API_CurtainWallSegmentID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::CurtainWallSegmentString; break; + case API_CurtainWallFrameID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::CurtainWallFrameString; break; + case API_CurtainWallPanelID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::CurtainWallPanelString; break; + case API_CurtainWallJunctionID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::CurtainWallJunctionString; break; + case API_CurtainWallAccessoryID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::CurtainWallAccessoryString; break; + case API_ShellID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::ShellString; break; + case API_SkylightID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::SkylightString; break; + case API_MorphID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::MorphString; break; + case API_ChangeMarkerID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::ChangeMarkerString; break; + case API_StairID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::StairString; break; + case API_RiserID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::RiserString; break; + case API_TreadID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::TreadString; break; + case API_StairStructureID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::StairStructureString; break; + case API_RailingID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::RailingString; break; + case API_RailingToprailID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::RailingToprailString; break; + case API_RailingHandrailID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::RailingHandrailString; break; + case API_RailingRailID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::RailingRailString; break; + case API_RailingPostID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::RailingPostString; break; + case API_RailingInnerPostID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::RailingInnerPostString; break; + case API_RailingBalusterID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::RailingBalusterString; break; + case API_RailingPanelID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::RailingPanelString; break; + case API_RailingSegmentID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::RailingSegmentString; break; + case API_RailingNodeID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::RailingNodeString; break; + case API_RailingBalusterSetID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::RailingBalusterSetString; break; + case API_RailingPatternID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::RailingPatternString; break; + case API_RailingToprailEndID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::RailingToprailEndString; break; + case API_RailingHandrailEndID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::RailingHandrailEndString; break; + case API_RailingRailEndID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::RailingRailEndString; break; + case API_RailingToprailConnectionID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::RailingToprailConnectionString; break; + case API_RailingHandrailConnectionID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::RailingHandrailConnectionString; break; + case API_RailingRailConnectionID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::RailingRailConnectionString; break; + case API_RailingEndFinishID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::RailingEndFinishString; break; +#ifndef ServerMainVers_2600 + case API_AnalyticalSupportID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::AnalyticalSupportString; break; + case API_AnalyticalLinkID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::AnalyticalLinkString; break; +#endif + case API_BeamSegmentID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::BeamSegmentString; break; + case API_ColumnSegmentID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::ColumnSegmentString; break; + case API_OpeningID: elementTypeStringItem = ResourceStrings::ElementTypeStringItems::OpeningString; break; default: - { return Error; } - } - typeName = GetElementTypeStringFromResource (elementTypeStringItems); + + return Error; +} + + +GS::ErrCode GetNonLocalizedElementTypeName (const API_Elem_Head& header, GS::UniString& typeName) +{ + ResourceStrings::ElementTypeStringItems elementTypeStringItem; + GetElementTypeStringItem (header, elementTypeStringItem); + typeName = GetFixElementTypeStringFromResource (elementTypeStringItem); + + return typeName.IsEmpty () ? Error : NoError; +} + + +GS::ErrCode GetLocalizedElementTypeName (const API_Elem_Head& header, GS::UniString& typeName) +{ +#ifdef ServerMainVers_2600 + return ACAPI_Goodies_GetElemTypeName (header.type, typeName); +#else + ResourceStrings::ElementTypeStringItems elementTypeStringItem; + GetElementTypeStringItem (header, elementTypeStringItem); + typeName = GetElementTypeStringFromResource (elementTypeStringItem); return typeName.IsEmpty () ? Error : NoError; #endif diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/Utility.hpp b/ConnectorArchicad/AddOn/Sources/AddOn/Utility.hpp index d0effed48f..27ff983335 100644 --- a/ConnectorArchicad/AddOn/Sources/AddOn/Utility.hpp +++ b/ConnectorArchicad/AddOn/Sources/AddOn/Utility.hpp @@ -13,7 +13,8 @@ namespace Utility { // Element Type API_ElemTypeID GetElementType (const API_Elem_Head& header); API_ElemTypeID GetElementType (const API_Guid& guid); -GS::ErrCode GetTypeNameFromElementType (const API_Elem_Head& header, GS::UniString& typeName); +GS::ErrCode GetNonLocalizedElementTypeName (const API_Elem_Head& header, GS::UniString& typeName); +GS::ErrCode GetLocalizedElementTypeName (const API_Elem_Head& header, GS::UniString& typeName); void SetElementType (API_Elem_Head& header, const API_ElemTypeID& elementType); bool ElementExists (const API_Guid& guid); diff --git a/ConnectorArchicad/AddOn/Sources/AddOnResources/RFIX/AddOnFix.grc b/ConnectorArchicad/AddOn/Sources/AddOnResources/RFIX/AddOnFix.grc index fc6db6e691..02d663f4d1 100644 --- a/ConnectorArchicad/AddOn/Sources/AddOnResources/RFIX/AddOnFix.grc +++ b/ConnectorArchicad/AddOn/Sources/AddOnResources/RFIX/AddOnFix.grc @@ -1,4 +1,5 @@ #include "ResourceMDIDIds.hpp" +#include "ResourceIds.hpp" 'MDID' 32500 "Add-On Identifier" { AC_MDID_DEV /* Set AC_MDID_DEV value as your developer id. */ @@ -8,3 +9,80 @@ 'GICN' 10001 "AddOnIcon" { "AddOnIcon" } + +'STR#' ID_FIX_ELEMENT_TYPE_STRINGS "Element Type Strings" { +/* [ 1] */ "Wall" +/* [ 2] */ "Column" +/* [ 3] */ "Beam" +/* [ 4] */ "Window" +/* [ 5] */ "Door" +/* [ 6] */ "Object" +/* [ 7] */ "Lamp" +/* [ 8] */ "Slab" +/* [ 9] */ "Roof" +/* [ 10] */ "Mesh" +/* [ 11] */ "Dimension" +/* [ 12] */ "Radial Dimension" +/* [ 13] */ "Level Dimension" +/* [ 14] */ "Angle Dimension" +/* [ 15] */ "Text" +/* [ 16] */ "Label" +/* [ 17] */ "Zone" +/* [ 18] */ "Hatch" +/* [ 19] */ "Line" +/* [ 10] */ "PolyLine" +/* [ 21] */ "Arc" +/* [ 22] */ "Circle" +/* [ 23] */ "Spline" +/* [ 24] */ "Hotspot" +/* [ 25] */ "CutPlane" +/* [ 26] */ "Camera" +/* [ 27] */ "CamSet" +/* [ 28] */ "Group" +/* [ 29] */ "SectElem" +/* [ 20] */ "Drawing" +/* [ 31] */ "Picture" +/* [ 32] */ "Detail" +/* [ 33] */ "Elevation" +/* [ 34] */ "Interior Elevation" +/* [ 35] */ "Worksheet" +/* [ 36] */ "Hotlink" +/* [ 37] */ "Curtain Wall" +/* [ 38] */ "Curtain Wall Segment" +/* [ 39] */ "Curtain Wall Frame" +/* [ 30] */ "Curtain Wall Panel" +/* [ 41] */ "Curtain Wall Junction" +/* [ 42] */ "Curtain Wall Accessory" +/* [ 43] */ "Shell" +/* [ 44] */ "Skylight" +/* [ 45] */ "Morph" +/* [ 46] */ "Change Marker" +/* [ 47] */ "Stair" +/* [ 48] */ "Riser" +/* [ 49] */ "Tread" +/* [ 40] */ "Stair Structure" +/* [ 51] */ "Railing" +/* [ 52] */ "Railing Toprail" +/* [ 53] */ "Railing Handrail" +/* [ 54] */ "Railing Rail" +/* [ 55] */ "Railing Post" +/* [ 56] */ "Railing Inner Post" +/* [ 57] */ "Railing Baluster" +/* [ 58] */ "Railing Panel" +/* [ 59] */ "Railing Segment" +/* [ 50] */ "Railing Node" +/* [ 61] */ "Railing Baluste rSet" +/* [ 62] */ "Railing Pattern" +/* [ 63] */ "Railing Toprail End" +/* [ 64] */ "Railing Handrail End" +/* [ 65] */ "Railing Rail End" +/* [ 66] */ "Railing Toprail Connection" +/* [ 67] */ "Railing Handrail Connection" +/* [ 68] */ "Railing Rail Connection" +/* [ 69] */ "Railing End Finish" +/* [ 70] */ "Analytical Support" +/* [ 71] */ "Analytical Link" +/* [ 72] */ "Beam Segment" +/* [ 73] */ "Column Segment" +/* [ 74] */ "Opening" +} diff --git a/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_GetElementIds.cs b/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_GetElementIds.cs index 0cc39587e7..8e38022ad4 100644 --- a/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_GetElementIds.cs +++ b/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_GetElementIds.cs @@ -12,7 +12,8 @@ internal sealed class GetElementIds : ICommand> public enum ElementFilter { All, - Selection + Selection, + ElementType } #region --- Classes --- @@ -26,13 +27,16 @@ public sealed class Parameters [JsonConverter(typeof(StringEnumConverter))] private ElementFilter Filter { get; } + [JsonProperty("filterBy")] + private List? FilterBy { get; } #endregion #region --- Ctor \ Dtor --- - public Parameters(ElementFilter filter) + public Parameters(ElementFilter filter, List? filterBy = null) { Filter = filter; + FilterBy = filterBy; } #endregion @@ -54,14 +58,16 @@ private sealed class Result #region --- Fields --- private ElementFilter Filter { get; } + private List? FilterBy { get; } #endregion #region --- Ctor \ Dtor --- - public GetElementIds(ElementFilter filter) + public GetElementIds(ElementFilter filter, List? filterBy = null) { Filter = filter; + FilterBy = filterBy; } #endregion @@ -70,7 +76,10 @@ public GetElementIds(ElementFilter filter) public async Task> Execute() { - Result result = await HttpCommandExecutor.Execute("GetElementIds", new Parameters(Filter)); + Result result = await HttpCommandExecutor.Execute( + "GetElementIds", + new Parameters(Filter, FilterBy) + ); return result.ApplicationIds; } diff --git a/ConnectorArchicad/ConnectorArchicad/ConnectorBinding.cs b/ConnectorArchicad/ConnectorArchicad/ConnectorBinding.cs index 2f331c6632..95182d9c0d 100644 --- a/ConnectorArchicad/ConnectorArchicad/ConnectorBinding.cs +++ b/ConnectorArchicad/ConnectorArchicad/ConnectorBinding.cs @@ -92,6 +92,34 @@ public override List GetSelectionFilters() Name = "Everything", Icon = "CubeScan", Description = "Sends all supported elements and project information." + }, + new ListSelectionFilter + { + Slug = "elementType", + Name = "Element Type", + Icon = "Category", + Values = new List + { + "Wall", + "Column", + "Beam", + "Slab", + "Roof", + "Shell", + "Stair", + "Railing", + "Curtain Wall", + "Door", + "Window", + "Skylight", + "Opening", + "Zone", + "Mesh", + "Morph", + "Object", + "Lamp" + }, + Description = "Adds all elements with the selected Element Types" } }; } diff --git a/ConnectorArchicad/ConnectorArchicad/Converters/ElementConverterManager.cs b/ConnectorArchicad/ConnectorArchicad/Converters/ElementConverterManager.cs index bf3f65411a..94ddd40680 100644 --- a/ConnectorArchicad/ConnectorArchicad/Converters/ElementConverterManager.cs +++ b/ConnectorArchicad/ConnectorArchicad/Converters/ElementConverterManager.cs @@ -60,6 +60,18 @@ private ElementConverterManager() elementIds = AsyncCommandProcessor .Execute(new Communication.Commands.GetElementIds(Communication.Commands.GetElementIds.ElementFilter.All)) ?.Result; + else if (filter.Slug == "elementType") + { + var elementTypes = filter.Summary.Split(",").Select(elementType => elementType.Trim()).ToList(); + elementIds = AsyncCommandProcessor + .Execute( + new Communication.Commands.GetElementIds( + Communication.Commands.GetElementIds.ElementFilter.ElementType, + elementTypes + ) + ) + ?.Result; + } SelectedObjects = await GetElementsType(elementIds, progress.CancellationToken); // Gets all selected objects SelectedObjects = SortSelectedObjects(); @@ -212,7 +224,14 @@ CancellationToken token { var subElementsAsBases = new List(); - if (convertedObject is not (Objects.BuiltElements.Archicad.ArchicadWall or Objects.BuiltElements.Archicad.ArchicadRoof or Objects.BuiltElements.Archicad.ArchicadShell)) + if ( + convertedObject + is not ( + Objects.BuiltElements.Archicad.ArchicadWall + or Objects.BuiltElements.Archicad.ArchicadRoof + or Objects.BuiltElements.Archicad.ArchicadShell + ) + ) return subElementsAsBases; var subElements = await GetAllSubElements(convertedObject.applicationId); From 64c6f6b7b92498a8bfda7d2319f114ab906d454c Mon Sep 17 00:00:00 2001 From: connorivy <43247197+connorivy@users.noreply.github.com> Date: Fri, 25 Aug 2023 07:39:13 -0500 Subject: [PATCH 16/57] Fix(Revit) : send slow down from parameter change (#2869) * revert units commit * add units symbol for parameters * remove unused code --------- Co-authored-by: Connor Ivy --- .../ConverterRevitShared/ConversionUtils.cs | 116 +++++++----------- .../ConverterRevitShared.projitems | 3 + .../Extensions/DefinitionExtensions.cs | 29 +++++ .../Extensions/DisplayUnitTypeExtensions.cs | 27 ++++ .../Extensions/ForgeTypeIdExtensions.cs | 31 +++++ .../ConvertAnalyticalSurface.cs | 2 +- .../Partial Classes/ConvertProfileWall.cs | 2 +- .../Partial Classes/ConvertRailing.cs | 2 +- .../Partial Classes/ConvertRoof.cs | 2 +- .../Objects/BuiltElements/Revit/Parameter.cs | 4 +- 10 files changed, 139 insertions(+), 79 deletions(-) create mode 100644 Objects/Converters/ConverterRevit/ConverterRevitShared/Extensions/DefinitionExtensions.cs create mode 100644 Objects/Converters/ConverterRevit/ConverterRevitShared/Extensions/DisplayUnitTypeExtensions.cs create mode 100644 Objects/Converters/ConverterRevit/ConverterRevitShared/Extensions/ForgeTypeIdExtensions.cs diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/ConversionUtils.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/ConversionUtils.cs index 2f25a49ef3..93c27ec5ca 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/ConversionUtils.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/ConversionUtils.cs @@ -5,10 +5,8 @@ using System.Text.RegularExpressions; using Autodesk.Revit.DB; using ConverterRevitShared.Extensions; -using Objects.BuiltElements; using Objects.BuiltElements.Revit; using Objects.Geometry; -using Objects.Organization; using Objects.Other; using RevitSharedResources.Interfaces; using Speckle.Core.Helpers; @@ -20,10 +18,8 @@ using DB = Autodesk.Revit.DB; using Level = Objects.BuiltElements.Level; using Line = Objects.Geometry.Line; -using OSG = Objects.Structural.Geometry; using Parameter = Objects.BuiltElements.Revit.Parameter; using Point = Objects.Geometry.Point; -using SHC = RevitSharedResources.Helpers.Categories; namespace Objects.Converter.Revit { @@ -345,11 +341,11 @@ private static Parameter ParameterToSpeckle( isShared = rp.IsShared, isReadOnly = rp.IsReadOnly, isTypeParameter = isTypeParameter, - applicationUnitType = definition.GetUnityTypeString(), //eg UT_Length - units = GetSymbolUnit(rp), + applicationUnitType = definition.GetUnityTypeString() //eg UT_Length }; - sp.value = GetParameterValue(rp, definition, out var appUnit, unitsOverride, cache); + sp.units = GetSymbolUnit(rp, definition, cache, out var unitTypeId); + sp.value = GetParameterValue(rp, definition, out var appUnit, unitsOverride, cache, unitTypeId); sp.applicationUnit = appUnit; return sp; } @@ -359,7 +355,12 @@ private static object GetParameterValue( Definition definition, out string unitType, string unitsOverride = null, - IRevitDocumentAggregateCache cache = null + IRevitDocumentAggregateCache cache = null, +#if REVIT2020 + DisplayUnitType unitTypeId = default +#else + ForgeTypeId unitTypeId = null +#endif ) { unitType = null; @@ -368,25 +369,20 @@ private static object GetParameterValue( case StorageType.Double: // NOTE: do not use p.AsDouble() as direct input for unit utils conversion, it doesn't work. ¯\_(ツ)_/¯ var val = rp.AsDouble(); - var unitTypeId = unitsOverride != null ? UnitsToNative(unitsOverride) : rp.GetUnitTypeId(); + if (unitsOverride == null) + { + unitTypeId = unitTypeId == default ? rp.GetUnitTypeId() : unitTypeId; + } + else + { + unitTypeId = UnitsToNative(unitsOverride); + } unitType = UnitsToNativeString(unitTypeId); return cache != null ? ScaleToSpeckle(val, unitTypeId, cache) : ScaleToSpeckleStatic(val, unitTypeId); case StorageType.Integer: var intVal = rp.AsInteger(); -#if REVIT2020 || REVIT2021 || REVIT2022 - switch (definition.ParameterType) - { - case ParameterType.YesNo: - return Convert.ToBoolean(intVal); - default: - return intVal; - } -#else - if (definition.GetDataType() == SpecTypeId.Boolean.YesNo) - return Convert.ToBoolean(intVal); - else - return intVal; -#endif + return definition.IsBool() ? Convert.ToBoolean(intVal) : intVal; + case StorageType.String: return rp.AsString(); // case StorageType.ElementId: @@ -404,61 +400,35 @@ private static object GetParameterValue( #endregion /// - /// Get the symbol unit of the parameter : eg. mm, m, cm, etc. + /// Method for getting symbol when parameter is NOT validated to be a double or int /// - /// the parameter of revit + /// + /// + /// + /// /// - public static string GetSymbolUnit(DB.Parameter parameter) - { - string symbol = string.Empty; + public static string GetSymbolUnit( + DB.Parameter parameter, + DB.Definition definition, + IRevitDocumentAggregateCache cache, #if REVIT2020 - try - { - DisplayUnitType forgeTypeId = parameter.DisplayUnitType; - IList validSymbols = FormatOptions.GetValidUnitSymbols(forgeTypeId); - if (validSymbols.Count > 0) - { - var unitSymbolTypes = validSymbols.Where(x => x != UnitSymbolType.UST_NONE).ToArray(); - if (unitSymbolTypes.Any()) - { - foreach (DB.UnitSymbolType symbolId in unitSymbolTypes) - { - symbol = LabelUtils.GetLabelFor(symbolId); - return symbol; - } - } - } - } - catch (Exception e) - { - // ignore with catch symbol - } + out DisplayUnitType unitTypeId #else - try - { - ForgeTypeId forgeTypeId = parameter.GetUnitTypeId(); - if (FormatOptions.CanHaveSymbol(forgeTypeId)) - { - IList validSymbols = FormatOptions.GetValidSymbols(forgeTypeId); - if (validSymbols.Count > 0) - { - IEnumerable typeId = validSymbols.Where(x => !x.Empty()); - if (typeId.Any()) - { - foreach (DB.ForgeTypeId symbolId in typeId) - { - symbol = LabelUtils.GetLabelForSymbol(symbolId); - } - } - } - } - } - catch (Exception e) + out ForgeTypeId unitTypeId +#endif + ) + { + unitTypeId = default; + if (parameter.StorageType != StorageType.Double) { - // ignore with catch symbol + return null; } -#endif - return symbol; + + unitTypeId = parameter.GetUnitTypeId(); + var unitTypeIdCopy = unitTypeId; + return cache + .GetOrInitializeEmptyCacheOfType(out _) + .GetOrAdd(unitTypeId.ToUniqueString(), () => unitTypeIdCopy.GetSymbol(), out _); } /// @@ -687,7 +657,7 @@ private Phase GetRevitPhase(DB.Document document, string phaseName) return null; } - #endregion +#endregion #region conversion "edit existing if possible" utilities diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/ConverterRevitShared.projitems b/Objects/Converters/ConverterRevit/ConverterRevitShared/ConverterRevitShared.projitems index cd64593d6e..ddf34a88ec 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/ConverterRevitShared.projitems +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/ConverterRevitShared.projitems @@ -18,8 +18,11 @@ + + + diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Extensions/DefinitionExtensions.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Extensions/DefinitionExtensions.cs new file mode 100644 index 0000000000..15a701af42 --- /dev/null +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Extensions/DefinitionExtensions.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Autodesk.Revit.DB; +using Objects.Primitive; + +namespace ConverterRevitShared.Extensions +{ + public static class DefinitionExtensions + { + public static bool IsBool(this Definition definition) + { +#if REVIT2020 || REVIT2021 || REVIT2022 + switch (definition.ParameterType) + { + case ParameterType.YesNo: + return true; + default: + return false; + } +#else + if (definition.GetDataType() == SpecTypeId.Boolean.YesNo) + return true; + else + return false; +#endif + } + } +} diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Extensions/DisplayUnitTypeExtensions.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Extensions/DisplayUnitTypeExtensions.cs new file mode 100644 index 0000000000..81fd7e3a04 --- /dev/null +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Extensions/DisplayUnitTypeExtensions.cs @@ -0,0 +1,27 @@ +#nullable enable +#if REVIT2020 +using System.Linq; +using Autodesk.Revit.DB; + +namespace ConverterRevitShared.Extensions +{ + internal static class DisplayUnitTypeExtensions + { + public static string? GetSymbol(this DisplayUnitType displayUnitType) + { + var validSymbols = FormatOptions.GetValidUnitSymbols(displayUnitType); + var unitSymbolTypes = validSymbols.Where(x => x != UnitSymbolType.UST_NONE); + foreach (var symbolId in unitSymbolTypes) + { + return LabelUtils.GetLabelFor(symbolId); + } + return null; + } + + public static string ToUniqueString(this DisplayUnitType displayUnitType) + { + return displayUnitType.ToString(); + } + } +} +#endif diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Extensions/ForgeTypeIdExtensions.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Extensions/ForgeTypeIdExtensions.cs new file mode 100644 index 0000000000..cf01b11fbe --- /dev/null +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Extensions/ForgeTypeIdExtensions.cs @@ -0,0 +1,31 @@ +#nullable enable +#if !REVIT2020 +using System.Linq; +using Autodesk.Revit.DB; +using DB = Autodesk.Revit.DB; + +namespace ConverterRevitShared.Extensions +{ + public static class ForgeTypeIdExtensions + { + public static string? GetSymbol(this ForgeTypeId forgeTypeId) + { + if (!FormatOptions.CanHaveSymbol(forgeTypeId)) + { + return null; + } + var validSymbols = FormatOptions.GetValidSymbols(forgeTypeId); + var typeId = validSymbols?.Where(x => !x.Empty()); + foreach (DB.ForgeTypeId symbolId in typeId) + { + return LabelUtils.GetLabelForSymbol(symbolId); + } + return null; + } + public static string ToUniqueString(this ForgeTypeId forgeTypeId) + { + return forgeTypeId.TypeId.ToString(); + } + } +} +#endif diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertAnalyticalSurface.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertAnalyticalSurface.cs index 9de0e70d51..fa52df7636 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertAnalyticalSurface.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertAnalyticalSurface.cs @@ -117,7 +117,7 @@ public ApplicationObject AnalyticalSurfaceToNative(Element2D speckleElement) // make sure that the bottom of the floor remains in the same location var newTypeDepth = GetParamValue(elementType, BuiltInParameter.FLOOR_ATTR_DEFAULT_THICKNESS_PARAM); - TrySetParam(physicalMember, BuiltInParameter.FLOOR_HEIGHTABOVELEVEL_PARAM, currentHeightOffset + (newTypeDepth - currentTypeDepth),""); + TrySetParam(physicalMember, BuiltInParameter.FLOOR_HEIGHTABOVELEVEL_PARAM, currentHeightOffset + (newTypeDepth - currentTypeDepth)); } } } diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertProfileWall.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertProfileWall.cs index 6ccf93951e..de38dbea02 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertProfileWall.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertProfileWall.cs @@ -61,7 +61,7 @@ public ApplicationObject ProfileWallToNative(RevitProfileWall speckleRevitWall) TrySetParam(revitWall, BuiltInParameter.WALL_BASE_CONSTRAINT, level); var offset = minZ - level.Elevation; - TrySetParam(revitWall, BuiltInParameter.WALL_BASE_OFFSET, offset,""); + TrySetParam(revitWall, BuiltInParameter.WALL_BASE_OFFSET, offset); if (revitWall.WallType.Name != wallType.Name) revitWall.ChangeTypeId(wallType.Id); diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertRailing.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertRailing.cs index e7943af50a..ae9cddaea9 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertRailing.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertRailing.cs @@ -63,7 +63,7 @@ public ApplicationObject RailingToNative(BuiltElements.Revit.RevitRailing speckl var topRailType = GetElementType(speckleRailing.topRail, appObj, out bool isTopRailExactMatch); if (GetParamValue(railingType, BuiltInParameter.RAILING_SYSTEM_HAS_TOP_RAIL) == 0) - TrySetParam(railingType, BuiltInParameter.RAILING_SYSTEM_HAS_TOP_RAIL, 1,""); + TrySetParam(railingType, BuiltInParameter.RAILING_SYSTEM_HAS_TOP_RAIL, 1); if (topRailType != null && isTopRailExactMatch) railingType.TopRailType = topRailType.Id; diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertRoof.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertRoof.cs index ac5ea3bb82..f457982b8f 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertRoof.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertRoof.cs @@ -136,7 +136,7 @@ public ApplicationObject RoofToNative(Roof speckleRoof) for (var i = 0; i < curveArray.Size; i++) revitFootprintRoof.set_DefinesSlope(curveArray.get_Item(i), true); - TrySetParam(revitFootprintRoof, BuiltInParameter.ROOF_SLOPE, (double)speckleFootprintRoof.slope,""); + TrySetParam(revitFootprintRoof, BuiltInParameter.ROOF_SLOPE, (double)speckleFootprintRoof.slope); } if (speckleFootprintRoof.cutOffLevel != null) diff --git a/Objects/Objects/BuiltElements/Revit/Parameter.cs b/Objects/Objects/BuiltElements/Revit/Parameter.cs index c6f20ba67c..18133943c9 100644 --- a/Objects/Objects/BuiltElements/Revit/Parameter.cs +++ b/Objects/Objects/BuiltElements/Revit/Parameter.cs @@ -14,7 +14,7 @@ public Parameter( [SchemaParamInfo( "(Optional) Speckle units. If not set it's retrieved from the current document. For non lenght based parameters (eg. Air Flow) it should be set to 'none' so that the Revit display unit will be used instead." )] - string units = "" + string units = "" ) { this.name = name; @@ -41,6 +41,6 @@ public Parameter( /// True = Type Parameter, False = Instance Parameter /// public bool isTypeParameter { get; set; } = false; - + public string units { get; set; } } From 562080cdb484cef56591fb595e6c57e868c7f377 Mon Sep 17 00:00:00 2001 From: connorivy <43247197+connorivy@users.noreply.github.com> Date: Fri, 25 Aug 2023 08:14:00 -0500 Subject: [PATCH 17/57] Feat(Revit) add receivable gis element (#2867) add gis polygon element and conversion Co-authored-by: Connor Ivy --- .../ConverterRevitShared/ConverterRevit.cs | 9 ++++ .../ConverterRevitShared.projitems | 1 + .../Partial Classes/ConvertPolygonElement.cs | 45 +++++++++++++++++++ Objects/Objects/GIS/PolygonElement.cs | 13 ++++++ Objects/Objects/Objects.csproj | 2 +- 5 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertPolygonElement.cs create mode 100644 Objects/Objects/GIS/PolygonElement.cs diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/ConverterRevit.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/ConverterRevit.cs index 3797453e11..188bcb3a74 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/ConverterRevit.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/ConverterRevit.cs @@ -3,6 +3,7 @@ using System.Linq; using Autodesk.Revit.DB; using Objects.BuiltElements.Revit; +using Objects.GIS; using Objects.Organization; using Objects.Structural.Properties.Profiles; using RevitSharedResources.Helpers; @@ -693,6 +694,12 @@ public object ConvertToNativeObject(Base @object) case Other.BlockInstance o: return BlockInstanceToNative(o); + // gis + case PolygonElement o: + return PolygonElementToNative(o); + + //hacky but the current comments camera is not a Base object + //used only from DUI and not for normal geometry conversion case Base b: //hacky but the current comments camera is not a Base object //used only from DUI and not for normal geometry conversion @@ -839,6 +846,8 @@ public bool CanConvertToNative(Base @object) STR.Geometry.Element2D _ => true, Other.BlockInstance _ => true, Organization.DataTable _ => true, + // GIS + PolygonElement _ => true, _ => false, }; if (objRes) diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/ConverterRevitShared.projitems b/Objects/Converters/ConverterRevit/ConverterRevitShared/ConverterRevitShared.projitems index ddf34a88ec..3982533125 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/ConverterRevitShared.projitems +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/ConverterRevitShared.projitems @@ -40,6 +40,7 @@ + diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertPolygonElement.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertPolygonElement.cs new file mode 100644 index 0000000000..1c4eee4844 --- /dev/null +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertPolygonElement.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Objects.BuiltElements.Revit; +using Objects.Geometry; +using Objects.GIS; +using Speckle.Core.Models; +using Speckle.Core.Models.GraphTraversal; + +namespace Objects.Converter.Revit +{ + public partial class ConverterRevit + { + public ApplicationObject PolygonElementToNative(PolygonElement polygonElement) + { + var speckleDirectShape = new Objects.BuiltElements.Revit.DirectShape() + { + applicationId = polygonElement.applicationId ??= Guid.NewGuid().ToString(), + baseGeometries = new List(), + parameters = new Base(), + name = "", + category = RevitCategory.GenericModel + }; + + var traversal = new GraphTraversal(DefaultTraversal.DefaultRule); + var meshes = traversal + .Traverse(polygonElement) + .Select(tc => tc.current) + .Where(b => b is Mesh); + + speckleDirectShape.baseGeometries.AddRange(meshes); + + foreach (var kvp in polygonElement.attributes.GetMembers()) + { + speckleDirectShape.parameters[kvp.Key] = new Objects.BuiltElements.Revit.Parameter() + { + name = kvp.Key, + value = kvp.Value + }; + } + + return DirectShapeToNative(speckleDirectShape, ToNativeMeshSettingEnum.Default); + } + } +} diff --git a/Objects/Objects/GIS/PolygonElement.cs b/Objects/Objects/GIS/PolygonElement.cs new file mode 100644 index 0000000000..5f2092e95c --- /dev/null +++ b/Objects/Objects/GIS/PolygonElement.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using Objects.BuiltElements.Revit; +using Speckle.Core.Models; + +namespace Objects.GIS +{ + public class PolygonElement : Base + { + [DetachProperty] + public List geometry { get; set; } + public Base attributes { get; set; } + } +} diff --git a/Objects/Objects/Objects.csproj b/Objects/Objects/Objects.csproj index 25822512aa..92216a78a2 100644 --- a/Objects/Objects/Objects.csproj +++ b/Objects/Objects/Objects.csproj @@ -18,6 +18,6 @@ - + From 2c3023bda27ca0bb092063b140cbb2085e40ecbf Mon Sep 17 00:00:00 2001 From: Matteo Cominetti Date: Fri, 25 Aug 2023 20:50:19 +0200 Subject: [PATCH 18/57] fix(dui): do not break the get streams loop if one server times out (#2860) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(dui): do not break the get streams loop if one server times out * add configurable timeout to http client factory * configurettimeout value overrides * fix(dui): Fixed regression with cancellation of searches --------- Co-authored-by: Gergő Jedlicska Co-authored-by: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> --- Core/Core/Api/GraphQL/Client.cs | 2 +- Core/Core/Helpers/Http.cs | 6 +++-- Core/Core/Transports/ServerUtils/ServerAPI.cs | 2 +- .../DesktopUI2/ViewModels/HomeViewModel.cs | 26 ++++++++++--------- 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/Core/Core/Api/GraphQL/Client.cs b/Core/Core/Api/GraphQL/Client.cs index 03e7f2b787..26f6da51f9 100644 --- a/Core/Core/Api/GraphQL/Client.cs +++ b/Core/Core/Api/GraphQL/Client.cs @@ -35,7 +35,7 @@ public Client(Account account) Account = account; - HttpClient = Http.GetHttpProxyClient(); + HttpClient = Http.GetHttpProxyClient(null, TimeSpan.FromSeconds(30)); Http.AddAuthHeader(HttpClient, account.token); HttpClient.DefaultRequestHeaders.Add("apollographql-client-name", Setup.HostApplication); diff --git a/Core/Core/Helpers/Http.cs b/Core/Core/Helpers/Http.cs index e77b9057c3..58eaf08ba0 100644 --- a/Core/Core/Helpers/Http.cs +++ b/Core/Core/Helpers/Http.cs @@ -176,12 +176,14 @@ public static async Task HttpPing(string address) } } - public static HttpClient GetHttpProxyClient(SpeckleHttpClientHandler? handler = null) + public static HttpClient GetHttpProxyClient(SpeckleHttpClientHandler? handler = null, TimeSpan? timeout = null) { IWebProxy proxy = WebRequest.GetSystemWebProxy(); proxy.Credentials = CredentialCache.DefaultCredentials; - return new HttpClient(handler ?? new SpeckleHttpClientHandler()); + var client = new HttpClient(handler ?? new SpeckleHttpClientHandler()); + client.Timeout = timeout ?? TimeSpan.FromSeconds(100); + return client; } public static bool CanAddAuth(string? authToken, out string? bearerHeader) diff --git a/Core/Core/Transports/ServerUtils/ServerAPI.cs b/Core/Core/Transports/ServerUtils/ServerAPI.cs index e864a745c4..3a06de8d1d 100644 --- a/Core/Core/Transports/ServerUtils/ServerAPI.cs +++ b/Core/Core/Transports/ServerUtils/ServerAPI.cs @@ -40,7 +40,7 @@ public ServerApi(string baseUri, string? authorizationToken, string blobStorageF ); _client.BaseAddress = new Uri(baseUri); - _client.Timeout = new TimeSpan(0, 0, timeoutSeconds); + _client.Timeout = TimeSpan.FromSeconds(timeoutSeconds); Http.AddAuthHeader(_client, authorizationToken); } diff --git a/DesktopUI2/DesktopUI2/ViewModels/HomeViewModel.cs b/DesktopUI2/DesktopUI2/ViewModels/HomeViewModel.cs index 3a52d8edf8..2bf03bcf66 100644 --- a/DesktopUI2/DesktopUI2/ViewModels/HomeViewModel.cs +++ b/DesktopUI2/DesktopUI2/ViewModels/HomeViewModel.cs @@ -204,14 +204,14 @@ private async Task GetStreams() } catch (OperationCanceledException) { - return; + continue; } - catch (Exception e) + catch (Exception ex) { - if (e.InnerException is TaskCanceledException) + if (ex.InnerException is TaskCanceledException) return; - SpeckleLog.Logger.Error(e, "Could not fetch streams"); + SpeckleLog.Logger.Error(ex, "Could not fetch streams"); Dispatcher.UIThread.Post( () => @@ -731,15 +731,17 @@ private async void OpenStreamCommand(object streamAccountWrapper) if (await CheckIsOffline().ConfigureAwait(true)) return; - - if (streamAccountWrapper != null) { var streamState = new StreamState(streamAccountWrapper as StreamAccountWrapper); if (!await streamState.Client.IsStreamAccessible(streamState.StreamId).ConfigureAwait(true)) { - Dialogs.ShowDialog("Stream not found", "Please ensure the stream exists and that you have access to it.", DialogIconKind.Error); + Dialogs.ShowDialog( + "Stream not found", + "Please ensure the stream exists and that you have access to it.", + DialogIconKind.Error + ); return; } @@ -755,16 +757,17 @@ private async void OpenSavedStreamCommand(object streamViewModel) if (await CheckIsOffline().ConfigureAwait(true)) return; - - if (streamViewModel != null && streamViewModel is StreamViewModel svm && !svm.NoAccess) { - try { if (!await svm.Client.IsStreamAccessible(svm.Stream.id).ConfigureAwait(true)) { - Dialogs.ShowDialog("Stream not found", "Please ensure the stream exists and that you have access to it.", DialogIconKind.Error); + Dialogs.ShowDialog( + "Stream not found", + "Please ensure the stream exists and that you have access to it.", + DialogIconKind.Error + ); return; } @@ -778,7 +781,6 @@ private async void OpenSavedStreamCommand(object streamViewModel) SpeckleLog.Logger.Error(ex, "Failed to open saved stream {exceptionMessage}", ex.Message); } } - } public void ToggleDarkThemeCommand() From faf081f9f5ab3cdafb25c2503bfbe92ed31b41b9 Mon Sep 17 00:00:00 2001 From: dtnaughton <77862637+dtnaughton@users.noreply.github.com> Date: Tue, 29 Aug 2023 05:49:45 -0400 Subject: [PATCH 19/57] fix(revit): add dynamic props to brace conversion (#2874) --- .../ConverterRevitShared/Partial Classes/ConvertBrace.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertBrace.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertBrace.cs index a9fb0e942f..83346bf453 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertBrace.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertBrace.cs @@ -50,7 +50,13 @@ private Base BraceToSpeckle(DB.FamilyInstance myFamily, out List notes) parameters = myBeam.parameters, displayValue = myBeam.displayValue, }; + + var dynamicProps = myBeam.GetMembers(DynamicBaseMemberType.Dynamic); + + foreach (var dp in dynamicProps) + myBrace[dp.Key] = dp.Value; + return myBrace; } } -} \ No newline at end of file +} From bf979d2626d68d1afff9cb21b4b042f0f91c6273 Mon Sep 17 00:00:00 2001 From: Matteo Cominetti Date: Wed, 30 Aug 2023 15:59:14 +0100 Subject: [PATCH 20/57] feat(revit): support for linked models with the view filter (#2870) * feat(revit): send linked model elemnts by view * fix(revit): use link instances and not link types * chore(revit): renames methods for consistency * add two checks for null linkedDocuments --------- Co-authored-by: Connor Ivy --- All.sln | 1 + .../UI/ConnectorBindingsRevit.Selection.cs | 230 +++++++----------- .../RevitSharedResources.projitems | 2 +- .../ConverterRevitShared/ConversionUtils.cs | 2 +- 4 files changed, 92 insertions(+), 143 deletions(-) diff --git a/All.sln b/All.sln index 6d06a8307c..f5732eac63 100644 --- a/All.sln +++ b/All.sln @@ -2127,6 +2127,7 @@ Global {8AD2EA4F-14FB-4BB6-94CD-932630DFED9C} = {C73C19B5-72A3-4C63-8D56-0A7E7DB46CA5} {EA34AC83-5825-4473-A572-D5127FD33B1B} = {C73C19B5-72A3-4C63-8D56-0A7E7DB46CA5} {521A7D9C-637F-4965-A6E6-BA96DF99807D} = {C73C19B5-72A3-4C63-8D56-0A7E7DB46CA5} + {61C1304B-ED48-456B-AB90-A89066187952} = {C73C19B5-72A3-4C63-8D56-0A7E7DB46CA5} {071F914C-F473-4FB2-9FAF-98632AFB164B} = {C73C19B5-72A3-4C63-8D56-0A7E7DB46CA5} {54E90327-5F48-468D-9349-17AACEAA0A77} = {25F45C77-279F-4608-86D1-87345EC42CB4} {6499CA05-6864-47AE-9204-B11B10C23417} = {25F45C77-279F-4608-86D1-87345EC42CB4} diff --git a/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Selection.cs b/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Selection.cs index 9cdeffbe34..88ead97ecb 100644 --- a/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Selection.cs +++ b/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Selection.cs @@ -168,31 +168,26 @@ public override void SelectClientObjects(List args, bool deselect = fals CurrentDoc.ShowElements(selection); } - private List GetLinkedDocuments() + private Dictionary GetLinkedDocuments() { - var docs = new List(); + var linkedDocs = new Dictionary(); // Get settings and return empty list if we should not send linked models var sendLinkedModels = CurrentSettings?.FirstOrDefault(x => x.Slug == "linkedmodels-send") as CheckBoxSetting; if (sendLinkedModels == null || !sendLinkedModels.IsChecked) - return docs; + return linkedDocs; - //TODO: is the name the most safe way to look for it? var linkedRVTs = new FilteredElementCollector(CurrentDoc.Document) .OfCategory(BuiltInCategory.OST_RvtLinks) - .OfClass(typeof(RevitLinkType)) + .OfClass(typeof(RevitLinkInstance)) .ToElements() - .Cast() - .Select(x => x.Name.Replace(".rvt", "")); - foreach (Document revitDoc in RevitApp.Application.Documents) + .Cast(); + foreach (var linkedRVT in linkedRVTs) { - if (revitDoc.IsLinked && linkedRVTs.Contains(revitDoc.Title)) - { - docs.Add(revitDoc); - } + linkedDocs.Add(linkedRVT.Id, linkedRVT.GetLinkDocument()); } - return docs; + return linkedDocs; } private static List FilterHiddenDesignOptions(List selection) @@ -231,9 +226,13 @@ private static List FilterHiddenDesignOptions(List selection) /// private List GetSelectionFilterObjects(ISpeckleConverter converter, ISelectionFilter filter) { - var currentDoc = CurrentDoc.Document; - var allDocs = GetLinkedDocuments(); - allDocs.Add(currentDoc); + var linkedDocs = GetLinkedDocuments(); + + var allDocs = new List + { + CurrentDoc.Document + }; + allDocs.AddRange(linkedDocs.Values); var selection = new List(); try @@ -241,14 +240,14 @@ private List GetSelectionFilterObjects(ISpeckleConverter converter, ISe switch (filter.Slug) { case "manual": - return GetManualSelection(filter, allDocs); + return GetManualSelection(filter, linkedDocs); case "all": - selection = GetEverything(currentDoc, allDocs); + selection = GetEverything(linkedDocs); return FilterHiddenDesignOptions(selection); case "category": - selection = GetSelectionByCategory(filter, currentDoc, allDocs); + selection = GetSelectionByCategory(filter, allDocs); return FilterHiddenDesignOptions(selection); case "filter": @@ -256,8 +255,8 @@ private List GetSelectionFilterObjects(ISpeckleConverter converter, ISe return FilterHiddenDesignOptions(selection); case "view": - var selectedViews = GetSelectedViews(filter, currentDoc); - selection = GetSelectionFromViews(selectedViews, allDocs); + var selectedViews = GetSelectedViews(filter); + selection = GetSelectionByView(selectedViews, linkedDocs); if (selectedViews.Count == 1) { // if the user is sending a single view, then we pass it to the converter in order for the converter @@ -271,18 +270,15 @@ private List GetSelectionFilterObjects(ISpeckleConverter converter, ISe } case "schedule": - return GetScheduleSelection(filter, currentDoc); + return GetSelectionBySchedule(filter); case "project-info": - return GetSelectionByProjectInfo(filter, currentDoc); + return GetSelectionByProjectInfo(filter); case "workset": - selection = GetSelectionByWorkset(filter, currentDoc, allDocs); + selection = GetSelectionByWorkset(filter, allDocs); return FilterHiddenDesignOptions(selection); - case "param": - return GetSelectionByParameter(filter, allDocs, selection); - default: throw new SpeckleException($"Unknown ISelectionFilterSlug, {filter.Slug}"); } @@ -296,25 +292,23 @@ private List GetSelectionFilterObjects(ISpeckleConverter converter, ISe } } - private static List GetManualSelection(ISelectionFilter filter, List allDocs) + private static List GetManualSelection(ISelectionFilter filter, Dictionary linkedDocs) { var selection = filter.Selection.Select(x => CurrentDoc.Document.GetElement(x)).Where(x => x != null).ToList(); - var linkedFiles = selection.Where(x => x is RevitLinkInstance).Cast().ToList(); + var selectedLinkedFiles = selection.Where(x => x is RevitLinkInstance).Cast().ToList(); - foreach (var linkedFile in linkedFiles) + foreach (var selectedLinkedFile in selectedLinkedFiles) { - var match = allDocs.FirstOrDefault( - x => x.Title == linkedFile.Name.Split(new string[] { ".rvt" }, StringSplitOptions.None)[0] - ); - if (match != null) - selection.AddRange(match.GetSupportedElements(revitDocumentAggregateCache)); + if (linkedDocs.ContainsKey(selectedLinkedFile.Id)) + selection.AddRange(linkedDocs[selectedLinkedFile.Id].GetSupportedElements(revitDocumentAggregateCache)); } return selection; } - private static List GetEverything(Document currentDoc, List allDocs) + private static List GetEverything(Dictionary linkedDocs) { + var currentDoc = CurrentDoc.Document; var selection = new List(); //add these only for the current doc if (!currentDoc.IsFamilyDocument) @@ -336,21 +330,20 @@ private static List GetEverything(Document currentDoc, List a selection.AddRange(currentDoc.Views2D()); selection.AddRange(currentDoc.Views3D()); + selection.AddRange(currentDoc.GetSupportedElements(revitDocumentAggregateCache)); + selection.AddRange(currentDoc.GetSupportedTypes(revitDocumentAggregateCache)); + //and these for every linked doc - foreach (var doc in allDocs) + foreach (var linkedDoc in linkedDocs.Values) { - selection.AddRange(doc.GetSupportedElements(revitDocumentAggregateCache)); // includes levels - selection.AddRange(doc.GetSupportedTypes(revitDocumentAggregateCache)); + selection.AddRange(linkedDoc.GetSupportedElements(revitDocumentAggregateCache)); // includes levels + selection.AddRange(linkedDoc.GetSupportedTypes(revitDocumentAggregateCache)); } return selection; } - private List GetSelectionByCategory( - ISelectionFilter filter, - Document currentDoc, - List allDocs - ) + private List GetSelectionByCategory(ISelectionFilter filter, List allDocs) { var selection = new List(); var catFilter = filter as ListSelectionFilter; @@ -367,6 +360,8 @@ List allDocs } using var categoryFilter = new ElementMulticategoryFilter(catIds); + + foreach (var doc in allDocs) { using var collector = new FilteredElementCollector(doc); @@ -429,42 +424,64 @@ private static List GetSelectionByFilter(ISelectionFilter filter, List< return selection; } - private static List GetSelectionFromViews( - List views, - List allDocs - ) + private static List GetSelectionByView(List views, Dictionary linkedDocs) { var selection = new List(); foreach (var view in views) { selection.Add(view); - var ids = selection.Select(x => x.UniqueId); - foreach (var doc in allDocs) + using var docCollector = new FilteredElementCollector(CurrentDoc.Document, view.Id); + selection.AddRange( + docCollector + .WhereElementIsNotElementType() + .WhereElementIsViewIndependent() + .Where(x => !selection.Any(s => s.UniqueId == x.UniqueId)) //exclude elements already added from other views + .ToList() + ); + + + foreach (var linkedDoc in linkedDocs) { - //NOTE: this logic needs revisiting, this is just to avoid the error: https://github.com/specklesystems/speckle-sharp/issues/2829 - if (doc.GetElement(view.Id) == null) + if (linkedDoc.Value == null) + { continue; - - using var docCollector = new FilteredElementCollector(doc, view.Id); + } + //from Revit 2024 onward we can query linked docs + //for earlier versions we can't: https://github.com/specklesystems/speckle-sharp/issues/2829 +#if !REVIT2020 && !REVIT2021 && !REVIT2022 && !REVIT2023 + using var linkedDocCollector = new FilteredElementCollector(CurrentDoc.Document, view.Id, linkedDoc.Key); selection.AddRange( - docCollector - .WhereElementIsNotElementType() - .WhereElementIsViewIndependent() - //.Where(x => x.IsPhysicalElement()) - .Where(x => !ids.Contains(x.UniqueId)) //exclude elements already added from other views - .ToList() - ); + linkedDocCollector + .WhereElementIsNotElementType() + .WhereElementIsViewIndependent() + //.Where(x => x.IsPhysicalElement()) + .Where(x => !selection.Any(s => s.UniqueId == x.UniqueId)) //exclude elements already added from other views + .ToList() + ); + +#else + //check if linked doc is visible in main doc + var linkedObject = CurrentDoc.Document.GetElement(linkedDoc.Key); + if (linkedObject.IsHidden(view)) + continue; + + //get ALL the linked model objects + selection + .AddRange(linkedDoc.Value.GetSupportedElements(revitDocumentAggregateCache) + .Where(x => !selection.Any(s => s.UniqueId == x.UniqueId))); +#endif + } } return selection; } - private static List GetSelectedViews(ISelectionFilter filter, Document currentDoc) + private static List GetSelectedViews(ISelectionFilter filter) { var selection = new List(); var viewFilter = filter as ListSelectionFilter; - using var collector = new FilteredElementCollector(currentDoc); + using var collector = new FilteredElementCollector(CurrentDoc.Document); using var scheduleExclusionFilter = new ElementClassFilter(typeof(ViewSchedule), true); return collector .WhereElementIsNotElementType() @@ -476,12 +493,12 @@ private static List GetSelectedViews(ISelectionFilter filter, Document cur .ToList(); } - private static List GetScheduleSelection(ISelectionFilter filter, Document currentDoc) + private static List GetSelectionBySchedule(ISelectionFilter filter) { var selection = new List(); var scheduleFilter = filter as ListSelectionFilter; - using var collector = new FilteredElementCollector(currentDoc); + using var collector = new FilteredElementCollector(CurrentDoc.Document); var schedules = collector .WhereElementIsNotElementType() .OfClass(typeof(ViewSchedule)) @@ -494,38 +511,37 @@ private static List GetScheduleSelection(ISelectionFilter filter, Docum return selection; } - private static List GetSelectionByProjectInfo(ISelectionFilter filter, Document currentDoc) + private static List GetSelectionByProjectInfo(ISelectionFilter filter) { var selection = new List(); var projectInfoFilter = filter as ListSelectionFilter; if (projectInfoFilter.Selection.Contains("Project Info")) - selection.Add(currentDoc.ProjectInformation); + selection.Add(CurrentDoc.Document.ProjectInformation); if (projectInfoFilter.Selection.Contains("Views 2D")) - selection.AddRange(currentDoc.Views2D()); + selection.AddRange(CurrentDoc.Document.Views2D()); if (projectInfoFilter.Selection.Contains("Views 3D")) - selection.AddRange(currentDoc.Views3D()); + selection.AddRange(CurrentDoc.Document.Views3D()); if (projectInfoFilter.Selection.Contains("Levels")) - selection.AddRange(currentDoc.Levels()); + selection.AddRange(CurrentDoc.Document.Levels()); if (projectInfoFilter.Selection.Contains("Families & Types")) - selection.AddRange(currentDoc.GetSupportedTypes(revitDocumentAggregateCache)); + selection.AddRange(CurrentDoc.Document.GetSupportedTypes(revitDocumentAggregateCache)); return selection; } private static List GetSelectionByWorkset( ISelectionFilter filter, - Document currentDoc, List allDocs ) { var selection = new List(); var worksetFilter = filter as ListSelectionFilter; - var worksets = new FilteredWorksetCollector(currentDoc) + var worksets = new FilteredWorksetCollector(CurrentDoc.Document) .Where(x => worksetFilter.Selection.Contains(x.Name)) .Select(x => x.Id) .ToList(); @@ -545,74 +561,6 @@ List allDocs return selection; } - private static List GetSelectionByParameter( - ISelectionFilter filter, - List allDocs, - List selection - ) - { - try - { - foreach (var doc in allDocs) - { - var propFilter = filter as PropertySelectionFilter; - using var collector = new FilteredElementCollector(doc); - var query = collector - .WhereElementIsNotElementType() - .WhereElementIsNotElementType() - .WhereElementIsViewIndependent() - .Where(x => x.IsPhysicalElement()) - .Where(fi => fi.LookupParameter(propFilter.PropertyName) != null); - - propFilter.PropertyValue = propFilter.PropertyValue.ToLowerInvariant(); - - switch (propFilter.PropertyOperator) - { - case "equals": - query = query.Where( - fi => GetStringValue(fi.LookupParameter(propFilter.PropertyName)) == propFilter.PropertyValue - ); - break; - case "contains": - query = query.Where( - fi => GetStringValue(fi.LookupParameter(propFilter.PropertyName)).Contains(propFilter.PropertyValue) - ); - break; - case "is greater than": - query = query.Where( - fi => - RevitVersionHelper.ConvertFromInternalUnits( - fi.LookupParameter(propFilter.PropertyName).AsDouble(), - fi.LookupParameter(propFilter.PropertyName) - ) > double.Parse(propFilter.PropertyValue) - ); - break; - case "is less than": - query = query.Where( - fi => - RevitVersionHelper.ConvertFromInternalUnits( - fi.LookupParameter(propFilter.PropertyName).AsDouble(), - fi.LookupParameter(propFilter.PropertyName) - ) < double.Parse(propFilter.PropertyValue) - ); - break; - } - - selection.AddRange(query.ToList()); - } - } - catch (Exception ex) - { - SpeckleLog.Logger.Error( - ex, - "Swallowing exception in {methodName}: {exceptionMessage}", - nameof(GetSelectionFilterObjects), - ex.Message - ); - } - return selection; - } - private static string GetStringValue(Parameter p) { string value = ""; diff --git a/ConnectorRevit/RevitSharedResources/RevitSharedResources.projitems b/ConnectorRevit/RevitSharedResources/RevitSharedResources.projitems index 8b6f7e2603..a73b4b37fa 100644 --- a/ConnectorRevit/RevitSharedResources/RevitSharedResources.projitems +++ b/ConnectorRevit/RevitSharedResources/RevitSharedResources.projitems @@ -33,4 +33,4 @@ - + \ No newline at end of file diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/ConversionUtils.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/ConversionUtils.cs index 93c27ec5ca..ffdcb53410 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/ConversionUtils.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/ConversionUtils.cs @@ -804,7 +804,7 @@ private DB.Transform GetReferencePointTransform(string type, Document doc) if (doc.IsLinked) { // get the linked doc instance transform - var instance = RevitLinkInstances.FirstOrDefault(x => x.GetLinkDocument().PathName == doc.PathName); + var instance = RevitLinkInstances.FirstOrDefault(x => x?.GetLinkDocument()?.PathName == doc.PathName); if (instance != null) { var linkInstanceTransform = instance.GetTotalTransform(); From e46a5f36b3faa953bd171071970651368fc1f1e6 Mon Sep 17 00:00:00 2001 From: Claire Kuang Date: Thu, 31 Aug 2023 11:32:01 +0100 Subject: [PATCH 21/57] feat(rhino): nested elements layers and parameters improvements when receiving revit commits (#2876) * adds special revit category case for layers creation * removes locked and hidden objects when receiving in update mode * adds parameters to instances and uses parameter name in key --- .../UI/ConnectorBindingsRhino.Receive.cs | 90 +++++----- .../ConverterRhinoGh.Other.cs | 160 +++++++++++------- .../ConverterRhinoGh.cs | 7 + 3 files changed, 153 insertions(+), 104 deletions(-) diff --git a/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Receive.cs b/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Receive.cs index 5d8f35bb88..bf5e9f50b2 100644 --- a/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Receive.cs +++ b/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Receive.cs @@ -207,7 +207,7 @@ public override async Task ReceiveStream(StreamState state, Progres if (state.ReceiveMode == ReceiveMode.Update) { toRemove = GetObjectsByApplicationId(previewObj.applicationId); - toRemove.ForEach(o => Doc.Objects.Delete(o)); + toRemove.ForEach(o => Doc.Objects.Delete(o, false, true)); if (!toRemove.Any()) // if no rhinoobjects were found, this could've been a view { @@ -282,9 +282,7 @@ private List GetObjectsByApplicationId(string applicationId) return new List(); // first try to find the object by app id user string - var match = - Doc.Objects.Where(o => o.Attributes.GetUserString(ApplicationIdKey) == applicationId)?.ToList() - ?? new List(); + var match = Doc.Objects.FindByUserString(ApplicationIdKey, applicationId, true).ToList(); // if nothing is found, look for the geom obj by its guid directly if (!match.Any()) @@ -336,18 +334,23 @@ ApplicationObject NewAppObj() if (current is Collection && string.IsNullOrEmpty(containerId)) return null; + // get parameters + var parameters = current["parameters"] as Base; + //Handle convertable objects if (converter.CanConvertToNative(current)) { var appObj = NewAppObj(); appObj.Convertible = true; - StoreObject(current, appObj); + StoreObject(current, appObj, parameters); return appObj; } //Handle objects convertable using displayValues - var fallbackMember = DefaultTraversal.displayValuePropAliases.Where(o => current[o] != null)?.FirstOrDefault(); - var parameters = current["parameters"] as Base; + var fallbackMember = DefaultTraversal.displayValuePropAliases + .Where(o => current[o] != null) + ?.Select(o => current[o]) + ?.FirstOrDefault(); if (fallbackMember != null) { var appObj = NewAppObj(); @@ -369,11 +372,28 @@ StringBuilder LayerIdRecurse(TraversalContext context, StringBuilder stringBuild if (context.propName == null) return stringBuilder; // this was probably the base commit collection + // handle elements hosting case from Revit + // WARNING: THIS IS REVIT-SPECIFIC CODE!! + // We are checking for the `Category` prop on children objects to use as the layer + if ( + DefaultTraversal.elementsPropAliases.Contains(context.propName) + && context.parent.current is not Collection + && !string.IsNullOrEmpty((string)context.current["category"]) + ) + { + stringBuilder.Append((string)context.current["category"]); + return stringBuilder; + } + string objectLayerName = string.Empty; - if (context.propName.ToLower() == "elements" && context.current is Collection coll) + + // handle collections case + if (context.current is Collection coll && DefaultTraversal.elementsPropAliases.Contains(context.propName)) objectLayerName = coll.name; - else if (context.propName.ToLower() != "elements") // this is for any other property on the collection. skip elements props in layer structure. + // handle default case + else if (!DefaultTraversal.elementsPropAliases.Contains(context.propName)) objectLayerName = context.propName[0] == '@' ? context.propName.Substring(1) : context.propName; + LayerIdRecurse(context.parent, stringBuilder); if (stringBuilder.Length != 0 && !string.IsNullOrEmpty(objectLayerName)) stringBuilder.Append(Layer.PathSeparator); @@ -461,6 +481,25 @@ private void BakeObject( // handle user info, including application id SetUserInfo(obj, attributes, parent); + // add revit parameters as user strings + var paramId = parent != null ? parent.OriginalId : obj.id; + if (StoredObjectParams.ContainsKey(paramId)) + { + var parameters = StoredObjectParams[paramId]; + foreach (var member in parameters.GetMembers(DynamicBaseMemberType.Dynamic)) + { + if (member.Value is Base parameter) + { + var convertedParameter = converter.ConvertToNative(parameter) as Tuple; + if (convertedParameter is not null) + { + var name = $"{convertedParameter.Item1}({member.Key})"; + attributes.SetUserString(name, convertedParameter.Item2); + } + } + } + } + Guid id = Doc.Objects.Add(o, attributes); if (id == Guid.Empty) { @@ -536,20 +575,6 @@ private void SetUserInfo(Base obj, ObjectAttributes attributes, ApplicationObjec } catch { } - // set parameters - if (parent != null) - if (StoredObjectParams.ContainsKey(parent.OriginalId)) - { - var parameters = StoredObjectParams[parent.OriginalId]; - foreach (var member in parameters.GetMembers(DynamicBaseMemberType.Dynamic)) - if (member.Value is Base parameter) - try - { - attributes.SetUserString(member.Key, GetStringFromBaseProp(parameter, "value")); - } - catch { } - } - // set user dictionaries if (obj[UserDictionary] is Base userDictionary) ParseDictionaryToArchivable(attributes.UserDictionary, userDictionary); @@ -622,23 +647,4 @@ private void ParseDictionaryToArchivable(ArchivableDictionary target, Base @base } } } - - private string GetStringFromBaseProp(Base @base, string propName) - { - var val = @base[propName]; - if (val == null) - return null; - switch (val) - { - case double o: - return o.ToString(); - case bool o: - return o.ToString(); - case int o: - return o.ToString(); - case string o: - return o; - } - return null; - } } diff --git a/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.Other.cs b/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.Other.cs index b02ec41a73..435e45de7d 100644 --- a/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.Other.cs +++ b/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.Other.cs @@ -24,7 +24,7 @@ namespace Objects.Converter.RhinoGh; public partial class ConverterRhinoGh { - // display and render + // display, render, and parameters public RH.ObjectAttributes DisplayStyleToNative(DisplayStyle display) { var attributes = new RH.ObjectAttributes(); @@ -241,6 +241,13 @@ public Other.RenderMaterial RenderMaterialToSpeckle(Rhino.Render.RenderMaterial return renderMaterial; } + public Tuple ParameterToNative(Objects.BuiltElements.Revit.Parameter parameter) + { + var name = parameter.name; + var val = parameter.value.ToString(); + return new Tuple(name, val); + } + // hatch public Hatch[] HatchToNative(Other.Hatch hatch) { @@ -448,7 +455,7 @@ public RH.InstanceDefinition DefinitionToNative(Base definition, out List - /// Traverses the object graph, returning objects that can be converted. - /// - /// The root object to traverse - /// A flattened list of objects to be converted ToNative - private Dictionary FlattenDefinitionObject(Base obj) - { - var StoredObjects = new Dictionary(); - - void StoreObject(Base current, string containerId) - { - //Handle convertable objects - if (CanConvertToNative(current)) - { - StoredObjects.Add(current, containerId); - return; - } - - //Handle objects convertable using displayValues - var fallbackMember = current["displayValue"] ?? current["@displayValue"]; - if (fallbackMember != null) - GraphTraversal.TraverseMember(fallbackMember).ToList().ForEach(o => StoreObject(o, containerId)); - } - - string LayerId(TraversalContext context) => LayerIdRecurse(context, new StringBuilder()).ToString(); - StringBuilder LayerIdRecurse(TraversalContext context, StringBuilder stringBuilder) - { - if (context.propName == null) - return stringBuilder; - - // see if there's a layer property on this obj - var layer = context.current["layer"] as string ?? context.current["Layer"] as string; - if (!string.IsNullOrEmpty(layer)) - return new StringBuilder(layer); - - var objectLayerName = context.propName[0] == '@' ? context.propName.Substring(1) : context.propName; - - LayerIdRecurse(context.parent, stringBuilder); - stringBuilder.Append(RH.Layer.PathSeparator); - stringBuilder.Append(objectLayerName); - - return stringBuilder; - } - - var traverseFunction = DefaultTraversal.CreateTraverseFunc(this); - - traverseFunction.Traverse(obj).ToList().ForEach(tc => StoreObject(tc.current, LayerId(tc))); - - return StoredObjects; - } - - #endregion - // Rhino convention seems to order the origin of the vector space last instead of first // This results in a transposed transformation matrix - may need to be addressed later public BlockInstance BlockInstanceToSpeckle(RH.InstanceObject instance) { var t = instance.InstanceXform; var matrix = new System.DoubleNumerics.Matrix4x4( - t.M00, t.M01, t.M02, t.M03, - t.M10, t.M11, t.M12, t.M13, - t.M20, t.M21, t.M22, t.M23, - t.M30, t.M31, t.M32, t.M33); + t.M00, + t.M01, + t.M02, + t.M03, + t.M10, + t.M11, + t.M12, + t.M13, + t.M20, + t.M21, + t.M22, + t.M23, + t.M30, + t.M31, + t.M32, + t.M33 + ); var def = BlockDefinitionToSpeckle(instance.InstanceDefinition); @@ -604,8 +569,24 @@ public ApplicationObject InstanceToNative(Instance instance, bool AppendToModelS // get the transform var transform = TransformToNative(instance.transform); + // get any parameters + var parameters = instance["parameters"] as Base; + var attributes = new RH.ObjectAttributes(); + if (parameters != null) + { + foreach (var member in parameters.GetMembers(DynamicBaseMemberType.Dynamic)) + { + if (member.Value is Parameter parameter) + { + var convertedParameter = ParameterToNative(parameter); + var name = $"{convertedParameter.Item1}({member.Key})"; + attributes.SetUserString(name, convertedParameter.Item2); + } + } + } + // create the instance - Guid instanceId = Doc.Objects.AddInstanceObject(instanceDef.Index, transform); + Guid instanceId = Doc.Objects.AddInstanceObject(instanceDef.Index, transform, attributes); if (instanceId == Guid.Empty) { @@ -622,6 +603,61 @@ public ApplicationObject InstanceToNative(Instance instance, bool AppendToModelS return appObj; } + #region instance methods + + /// + /// Traverses the object graph, returning objects that can be converted. + /// + /// The root object to traverse + /// A flattened list of objects to be converted ToNative + private Dictionary FlattenDefinitionObject(Base obj) + { + var StoredObjects = new Dictionary(); + + void StoreObject(Base current, string containerId) + { + //Handle convertable objects + if (CanConvertToNative(current)) + { + StoredObjects.Add(current, containerId); + return; + } + + //Handle objects convertable using displayValues + var fallbackMember = current["displayValue"] ?? current["@displayValue"]; + if (fallbackMember != null) + GraphTraversal.TraverseMember(fallbackMember).ToList().ForEach(o => StoreObject(o, containerId)); + } + + string LayerId(TraversalContext context) => LayerIdRecurse(context, new StringBuilder()).ToString(); + StringBuilder LayerIdRecurse(TraversalContext context, StringBuilder stringBuilder) + { + if (context.propName == null) + return stringBuilder; + + // see if there's a layer property on this obj + var layer = context.current["layer"] as string ?? context.current["Layer"] as string; + if (!string.IsNullOrEmpty(layer)) + return new StringBuilder(layer); + + var objectLayerName = context.propName[0] == '@' ? context.propName.Substring(1) : context.propName; + + LayerIdRecurse(context.parent, stringBuilder); + stringBuilder.Append(RH.Layer.PathSeparator); + stringBuilder.Append(objectLayerName); + + return stringBuilder; + } + + var traverseFunction = DefaultTraversal.CreateTraverseFunc(this); + + traverseFunction.Traverse(obj).ToList().ForEach(tc => StoreObject(tc.current, LayerId(tc))); + + return StoredObjects; + } + + #endregion + public DisplayMaterial RenderMaterialToDisplayMaterial(Other.RenderMaterial material) { var rhinoMaterial = new RH.Material diff --git a/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.cs b/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.cs index 9b66088faf..e8915d2182 100644 --- a/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.cs +++ b/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.cs @@ -508,9 +508,15 @@ public object ConvertToNative(Base @object) rhinoObj = RenderMaterialToNative(o); #endif break; + case Transform o: rhinoObj = TransformToNative(o); break; + + case Parameter o: + rhinoObj = ParameterToNative(o); + break; + default: if (reportObj != null) { @@ -545,6 +551,7 @@ public object ConvertToNative(Base @object) log: o.Log ); break; + default: if (reportObj != null) reportObj.Update(log: notes); From 9782e892f81467199c34bc5974614b7e7486957d Mon Sep 17 00:00:00 2001 From: Claire Kuang Date: Mon, 4 Sep 2023 08:25:17 +0100 Subject: [PATCH 22/57] feat(rhino): adds level to native conversion (#2883) * adds level conversion as named construction planes * removes existing levels on receive also includes small refactor of removing existing views * Update ConnectorBindingsRhino.Receive.cs --- .../UI/ConnectorBindingsRhino.Previews.cs | 1 + .../UI/ConnectorBindingsRhino.Receive.cs | 47 ++++++++++++------- .../ConverterRhinoGh.BuiltElements.cs | 25 ++++++++++ .../ConverterRhinoGh.cs | 5 ++ 4 files changed, 62 insertions(+), 16 deletions(-) diff --git a/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Previews.cs b/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Previews.cs index fa073565d1..a93ccb7bde 100644 --- a/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Previews.cs +++ b/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Previews.cs @@ -88,6 +88,7 @@ private static bool IsPreviewIgnore(Base @object) { return @object.speckle_type.Contains("Instance") || @object.speckle_type.Contains("View") + || @object.speckle_type.Contains("Level") || @object.speckle_type.Contains("Collection"); } diff --git a/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Receive.cs b/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Receive.cs index bf5e9f50b2..104a2a0571 100644 --- a/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Receive.cs +++ b/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Receive.cs @@ -209,14 +209,25 @@ public override async Task ReceiveStream(StreamState state, Progres toRemove = GetObjectsByApplicationId(previewObj.applicationId); toRemove.ForEach(o => Doc.Objects.Delete(o, false, true)); - if (!toRemove.Any()) // if no rhinoobjects were found, this could've been a view + if (!toRemove.Any()) // if no rhinoobjects were found, this could've been a view or level (named construction plane) { - var viewId = Doc.NamedViews.FindByName(previewObj.applicationId); + // Check converter (ViewToNative and LevelToNative) to make sure these names correspond! + var name = + state.ReceiveMode == ReceiveMode.Create + ? $"{commitLayerName} - {previewObj.applicationId}" + : previewObj.applicationId; + var viewId = Doc.NamedViews.FindByName(name); + var planeId = Doc.NamedConstructionPlanes.Find(name); if (viewId != -1) { isUpdate = true; Doc.NamedViews.Delete(viewId); } + else if (planeId != -1) + { + isUpdate = true; + Doc.NamedConstructionPlanes.Delete(planeId); + } } } if (toRemove.Count() > 0) @@ -307,9 +318,7 @@ private List FlattenCommitObject(Base obj, ISpeckleConverter { void StoreObject(Base @base, ApplicationObject appObj, Base parameters = null) { - if (StoredObjects.ContainsKey(@base.id)) - appObj.Update(logItem: "Found another object in this commit with the same id. Skipped other object"); //TODO check if we are actually ignoring duplicates, since we are returning the app object anyway... - else + if (!StoredObjects.ContainsKey(@base.id)) StoredObjects.Add(@base.id, @base); if (parameters != null && !StoredObjectParams.ContainsKey(@base.id)) @@ -323,11 +332,15 @@ ApplicationObject NewAppObj() var speckleType = current.speckle_type .Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries) .LastOrDefault(); - return new ApplicationObject(current.id, speckleType) - { - applicationId = current.applicationId, - Container = containerId - }; + + // get the application id and container + // special cases for views and levels, since we are searching for them by name instead of application id + var applicationId = + speckleType.Contains("View") || speckleType.Contains("Level") + ? current["name"] as string + : current.applicationId; + var container = speckleType.Contains("View") || speckleType.Contains("Level") ? null : containerId; + return new ApplicationObject(current.id, speckleType) { applicationId = applicationId, Container = container }; } // skip if it is the base commit collection @@ -530,23 +543,25 @@ private void BakeObject( } } break; - case RhinoObject o: // this was prbly a block instance, baked during conversion + case RhinoObject o: // this was prbly a block instance, baked during conversion o.Attributes.LayerIndex = layer.Index; // assign layer SetUserInfo(obj, o.Attributes, parent); // handle user info, including application id o.CommitChanges(); - if (parent != null) parent.Update(o.Id.ToString()); else appObj.Update(o.Id.ToString()); bakedCount++; break; + case ViewInfo o: // this is a view, baked during conversion - if (parent != null) - parent.Update(o.Name); - else - appObj.Update(o.Name); + appObj.Update(o.Name); + bakedCount++; + break; + + case ConstructionPlane o: // this is a level, baked during conversion + appObj.Update(o.Name); bakedCount++; break; } diff --git a/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.BuiltElements.cs b/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.BuiltElements.cs index f5cd2ff5b4..396ab7495a 100644 --- a/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.BuiltElements.cs +++ b/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.BuiltElements.cs @@ -173,6 +173,31 @@ public List DirectShapeToNative(RV.DirectShape directShape, out List Date: Mon, 4 Sep 2023 08:25:52 +0100 Subject: [PATCH 23/57] feat(rhino): adds setting to merge all coplanar faces when receiving meshes (#2881) * adds clean mesh setting to rhino receive * fix(rh): Rolled back mesh settings removal Flagged it as obsolete instead. There are too many components depending on this behaviour and I wasn't sure i would break something while updating it. --------- Co-authored-by: Alan Rynne --- .../ConnectorGrasshopper7.csproj | 18 ++++---- .../ConnectorRhinoShared.projitems | 1 + .../UI/ConnectorBindingsRhino.Previews.cs | 5 +++ .../UI/ConnectorBindingsRhino.Receive.cs | 5 +++ .../UI/ConnectorBindingsRhino.Send.cs | 5 +++ .../UI/ConnectorBindingsRhino.Settings.cs | 43 +++++++++++++++++++ .../UI/ConnectorBindingsRhino.cs | 16 ++----- .../ConnectorRhino7/ConnectorRhino7.csproj | 32 ++++++-------- .../ConverterGrasshopper7.csproj | 10 ++--- .../ConverterRhino7/ConverterRhino7.csproj | 6 +-- .../ConverterRhinoGh.Geometry.cs | 35 +++++++-------- .../ConverterRhinoGh.cs | 21 ++++----- 12 files changed, 120 insertions(+), 77 deletions(-) create mode 100644 ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Settings.cs diff --git a/ConnectorGrasshopper/ConnectorGrasshopper7/ConnectorGrasshopper7.csproj b/ConnectorGrasshopper/ConnectorGrasshopper7/ConnectorGrasshopper7.csproj index 1d1b796deb..dd8b147826 100644 --- a/ConnectorGrasshopper/ConnectorGrasshopper7/ConnectorGrasshopper7.csproj +++ b/ConnectorGrasshopper/ConnectorGrasshopper7/ConnectorGrasshopper7.csproj @@ -18,22 +18,22 @@ $(DefineConstants);MAC - + - - - + + + - - - + + + - - + + diff --git a/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/ConnectorRhinoShared.projitems b/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/ConnectorRhinoShared.projitems index a3cd4baa53..5169402b04 100644 --- a/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/ConnectorRhinoShared.projitems +++ b/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/ConnectorRhinoShared.projitems @@ -17,6 +17,7 @@ True True + diff --git a/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Previews.cs b/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Previews.cs index a93ccb7bde..fda4f2f307 100644 --- a/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Previews.cs +++ b/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Previews.cs @@ -104,6 +104,11 @@ public override async Task PreviewReceive(StreamState state, Progre var converter = KitManager.GetDefaultKit().LoadConverter(Utils.RhinoAppName); converter.SetContextDocument(Doc); + // set converter settings + CurrentSettings = state.Settings; + var settings = GetSettingsDict(CurrentSettings); + converter.SetConverterSettings(settings); + var commitObject = await ConnectorHelpers.ReceiveCommit(commit, state, progress); SelectedReceiveCommit = commit.id; diff --git a/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Receive.cs b/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Receive.cs index 104a2a0571..f5abbb3348 100644 --- a/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Receive.cs +++ b/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Receive.cs @@ -36,6 +36,11 @@ public override async Task ReceiveStream(StreamState state, Progres converter.SetContextDocument(Doc); converter.ReceiveMode = state.ReceiveMode; + // set converter settings + CurrentSettings = state.Settings; + var settings = GetSettingsDict(CurrentSettings); + converter.SetConverterSettings(settings); + Commit commit = await ConnectorHelpers.GetCommitFromState(state, progress.CancellationToken); state.LastCommit = commit; diff --git a/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Send.cs b/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Send.cs index 8e0fa642ee..625157eacd 100644 --- a/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Send.cs +++ b/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Send.cs @@ -24,6 +24,11 @@ public override async Task SendStream(StreamState state, ProgressViewMod var converter = KitManager.GetDefaultKit().LoadConverter(Utils.RhinoAppName); converter.SetContextDocument(Doc); + // set converter settings + CurrentSettings = state.Settings; + var settings = GetSettingsDict(CurrentSettings); + converter.SetConverterSettings(settings); + var streamId = state.StreamId; var client = state.Client; diff --git a/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Settings.cs b/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Settings.cs new file mode 100644 index 0000000000..694aa2b3fa --- /dev/null +++ b/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.Settings.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using DesktopUI2; +using DesktopUI2.Models.Settings; +using Rhino; +using Rhino.DocObjects.Tables; + +namespace SpeckleRhino; + +public partial class ConnectorBindingsRhino : ConnectorBindings +{ + const string defaultValue = "Default"; + const string mergeCoplanar = "Merge Coplanar Faces"; + + public override List GetSettings() + { + List meshImportOptions = new List() { defaultValue, mergeCoplanar }; + + return new List + { + new ListBoxSetting + { + Slug = "receive-mesh", + Name = "Mesh Import Method", + Icon = "ChartTimelineVarient", + Values = meshImportOptions, + Selection = defaultValue, + Description = "Determines the method of importing meshes" + } + }; + } + + /// + /// Converts the settings to (setting slug, setting selection) key value pairs + /// + /// + public Dictionary GetSettingsDict(List? currentSettings) + { + var settings = new Dictionary(); + foreach (var setting in currentSettings) + settings.Add(setting.Slug, setting.Selection); + return settings; + } +} diff --git a/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.cs b/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.cs index 9bba54181a..8e1201f74b 100644 --- a/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.cs +++ b/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/ConnectorBindingsRhino.cs @@ -37,6 +37,9 @@ public override List GetReceiveModes() return new List { ReceiveMode.Update, ReceiveMode.Create }; } + // used to store the Stream State settings when sending/receiving + private List? CurrentSettings { get; set; } + #region Local streams I/O with local file public override List GetStreamsInFile() @@ -116,19 +119,6 @@ private void LogUnsupportedObjects(List objs, ISpeckleConverter con RhinoApp.WriteLine($"{entry.Value} of type {entry.Key}"); } - public override List GetSettings() - { - /* - var referencePoints = new List() { "Internal Origin (default)" }; - referencePoints.AddRange(Doc.NamedConstructionPlanes.Select(o => o.Name).ToList()); - return new List() - { - new ListBoxSetting {Slug = "reference-point", Name = "Reference Point", Icon ="LocationSearching", Values = referencePoints, Description = "Receives stream objects in relation to this document point"} - }; - */ - return new List(); - } - public override void ResetDocument() { if (PreviewConduit != null) diff --git a/ConnectorRhino/ConnectorRhino7/ConnectorRhino7.csproj b/ConnectorRhino/ConnectorRhino7/ConnectorRhino7.csproj index 8eb0f3ade2..c7425b783b 100644 --- a/ConnectorRhino/ConnectorRhino7/ConnectorRhino7.csproj +++ b/ConnectorRhino/ConnectorRhino7/ConnectorRhino7.csproj @@ -36,40 +36,32 @@ Program - + - + - - - + + + - - - + + + - - + + - - + + \ No newline at end of file diff --git a/Objects/Converters/ConverterRhinoGh/ConverterGrasshopper7/ConverterGrasshopper7.csproj b/Objects/Converters/ConverterRhinoGh/ConverterGrasshopper7/ConverterGrasshopper7.csproj index de3be980c1..092faf6b03 100644 --- a/Objects/Converters/ConverterRhinoGh/ConverterGrasshopper7/ConverterGrasshopper7.csproj +++ b/Objects/Converters/ConverterRhinoGh/ConverterGrasshopper7/ConverterGrasshopper7.csproj @@ -11,18 +11,18 @@ $(DefineConstants);RHINO7;GRASSHOPPER - + - + - + - - + + diff --git a/Objects/Converters/ConverterRhinoGh/ConverterRhino7/ConverterRhino7.csproj b/Objects/Converters/ConverterRhinoGh/ConverterRhino7/ConverterRhino7.csproj index 6df4cc42cf..2376611602 100644 --- a/Objects/Converters/ConverterRhinoGh/ConverterRhino7/ConverterRhino7.csproj +++ b/Objects/Converters/ConverterRhinoGh/ConverterRhino7/ConverterRhino7.csproj @@ -15,11 +15,11 @@ - + - + @@ -39,5 +39,5 @@ x64 - + diff --git a/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.Geometry.cs b/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.Geometry.cs index 0c3cb96abe..285e8fb09e 100644 --- a/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.Geometry.cs +++ b/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.Geometry.cs @@ -724,22 +724,19 @@ public RH.Mesh MeshToNative(Mesh mesh) } m.Faces.CullDegenerateFaces(); - return m; - } - - private bool HasInvalidMultiplicity(RH.NurbsCurve curve) - { - var knots = curve.Knots; - var degree = curve.Degree; +#if RHINO7 + // get receive mesh setting + var meshSetting = Settings.ContainsKey("receive-mesh") + ? Settings["receive-mesh"] + : string.Empty; - for (int i = degree; i < knots.Count - degree; i++) + if (meshSetting == "Merge Coplanar Faces") { - var mult = knots.KnotMultiplicity(i); - i += mult - 1; - if (mult > degree - 2) - return true; + m.MergeAllCoplanarFaces(Doc.ModelAbsoluteTolerance, Doc.ModelAngleToleranceRadians); } - return false; +#endif + + return m; } // Pointcloud @@ -910,15 +907,19 @@ private RH.Mesh GetBrepDisplayMesh(RH.Brep brep) { var joinedMesh = new RH.Mesh(); - var mySettings = RH.MeshingParameters.Default; + // get from settings + //Settings.TryGetValue("sendMeshSetting", out string meshSetting); + + RH.MeshingParameters mySettings; switch (SelectedMeshSettings) { - case MeshSettings.Default: - mySettings = new RH.MeshingParameters(0.05, 0.05); - break; case MeshSettings.CurrentDoc: mySettings = RH.MeshingParameters.DocumentCurrentSetting(Doc); break; + case MeshSettings.Default: + default: + mySettings = new RH.MeshingParameters(0.05, 0.05); + break; } try diff --git a/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.cs b/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.cs index 6b285e923e..f42dc4a9d6 100644 --- a/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.cs +++ b/Objects/Converters/ConverterRhinoGh/ConverterRhinoGhShared/ConverterRhinoGh.cs @@ -40,16 +40,20 @@ public partial class ConverterRhinoGh : ISpeckleConverter public static string RhinoAppName = HostApplications.Rhino.GetVersion(HostAppVersion.v7); #endif + [Obsolete] public enum MeshSettings { Default, CurrentDoc } + [Obsolete] public MeshSettings SelectedMeshSettings = MeshSettings.Default; public bool PreprocessGeometry; + public Dictionary Settings { get; private set; } = new Dictionary(); + public ConverterRhinoGh() { var ver = Assembly.GetAssembly(typeof(ConverterRhinoGh)).GetName().Version; @@ -88,17 +92,12 @@ public void SetPreviousContextObjects(List objects) public void SetConverterSettings(object settings) { - if (settings is Dictionary dict) - { - if (dict.ContainsKey("meshSettings")) - SelectedMeshSettings = (MeshSettings)dict["meshSettings"]; + if (settings is Dictionary temp) + Settings = temp; - if (dict.ContainsKey("preprocessGeometry")) - PreprocessGeometry = (bool)dict["preprocessGeometry"]; - return; - } - - // Keep this for backwards compatibility. + // TODO: Both settings bellow are here for backwards compatibility and should be removed after consolidating settings + if (Settings.TryGetValue("preprocessGeometry", out string setting)) + bool.TryParse(setting, out PreprocessGeometry); var s = (MeshSettings)settings; SelectedMeshSettings = s; } @@ -145,6 +144,8 @@ public Base ConvertToSpeckle(object @object) Base @base = null; Base schema = null; var notes = new List(); + + // get preprocessing setting var defaultPreprocess = PreprocessGeometry; try From 823769973b6fdb4fc5fc4f5fbdf28f8766c05fd0 Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Mon, 4 Sep 2023 13:45:15 +0100 Subject: [PATCH 24/57] Performance test project (#2847) * Performance test experimentation * traversal performance test * test(perf): Deserailizer worker threads parallelisation tests * net48 test --------- Co-authored-by: Alan Rynne --- All.sln | 19 +++++++ Core/Core/Logging/LoggingHelpers.cs | 16 ++++++ Core/Core/Models/DynamicBase.cs | 56 +++++++----------- .../Serialisation/BaseObjectDeserializerV2.cs | 31 +++++----- .../DeserializationWorkerThreads.cs | 4 +- Core/Core/Transports/SQLite.cs | 8 +-- .../Api/Operations/ReceiveFromSQLite.cs | 44 ++++++++++++++ .../Api/Operations/TraverseCommit.cs | 38 +++++++++++++ Core/TestsPerformance/Program.cs | 11 ++++ Core/TestsPerformance/RegressionTestConfig.cs | 55 ++++++++++++++++++ .../DeserializationWorkerThreads.cs | 39 +++++++++++++ Core/TestsPerformance/TestDataHelper.cs | 57 +++++++++++++++++++ Core/TestsPerformance/TestsPerformance.csproj | 18 ++++++ 13 files changed, 342 insertions(+), 54 deletions(-) create mode 100644 Core/Core/Logging/LoggingHelpers.cs create mode 100644 Core/TestsPerformance/Api/Operations/ReceiveFromSQLite.cs create mode 100644 Core/TestsPerformance/Api/Operations/TraverseCommit.cs create mode 100644 Core/TestsPerformance/Program.cs create mode 100644 Core/TestsPerformance/RegressionTestConfig.cs create mode 100644 Core/TestsPerformance/Serialisation/DeserializationWorkerThreads.cs create mode 100644 Core/TestsPerformance/TestDataHelper.cs create mode 100644 Core/TestsPerformance/TestsPerformance.csproj diff --git a/All.sln b/All.sln index f5732eac63..0b60654940 100644 --- a/All.sln +++ b/All.sln @@ -364,6 +364,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConnectorTeklaStructures202 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConverterTeklaStructures2023", "Objects\Converters\ConverterTeklaStructures\ConverterTeklaStructures2023\ConverterTeklaStructures2023.csproj", "{EB52E451-9ED8-460E-9EE4-6717BFB12EAB}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestsPerformance", "Core\TestsPerformance\TestsPerformance.csproj", "{4D1C70D7-FFD5-4518-A374-2A23E020D416}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug Mac|Any CPU = Debug Mac|Any CPU @@ -1985,6 +1987,22 @@ Global {EB52E451-9ED8-460E-9EE4-6717BFB12EAB}.Release|Any CPU.Build.0 = Release|Any CPU {EB52E451-9ED8-460E-9EE4-6717BFB12EAB}.Release|x64.ActiveCfg = Release|Any CPU {EB52E451-9ED8-460E-9EE4-6717BFB12EAB}.Release|x64.Build.0 = Release|Any CPU + {4D1C70D7-FFD5-4518-A374-2A23E020D416}.Debug Mac|Any CPU.ActiveCfg = Debug|Any CPU + {4D1C70D7-FFD5-4518-A374-2A23E020D416}.Debug Mac|Any CPU.Build.0 = Debug|Any CPU + {4D1C70D7-FFD5-4518-A374-2A23E020D416}.Debug Mac|x64.ActiveCfg = Debug|Any CPU + {4D1C70D7-FFD5-4518-A374-2A23E020D416}.Debug Mac|x64.Build.0 = Debug|Any CPU + {4D1C70D7-FFD5-4518-A374-2A23E020D416}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4D1C70D7-FFD5-4518-A374-2A23E020D416}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4D1C70D7-FFD5-4518-A374-2A23E020D416}.Debug|x64.ActiveCfg = Debug|Any CPU + {4D1C70D7-FFD5-4518-A374-2A23E020D416}.Debug|x64.Build.0 = Debug|Any CPU + {4D1C70D7-FFD5-4518-A374-2A23E020D416}.Release Mac|Any CPU.ActiveCfg = Debug|Any CPU + {4D1C70D7-FFD5-4518-A374-2A23E020D416}.Release Mac|Any CPU.Build.0 = Debug|Any CPU + {4D1C70D7-FFD5-4518-A374-2A23E020D416}.Release Mac|x64.ActiveCfg = Debug|Any CPU + {4D1C70D7-FFD5-4518-A374-2A23E020D416}.Release Mac|x64.Build.0 = Debug|Any CPU + {4D1C70D7-FFD5-4518-A374-2A23E020D416}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4D1C70D7-FFD5-4518-A374-2A23E020D416}.Release|Any CPU.Build.0 = Release|Any CPU + {4D1C70D7-FFD5-4518-A374-2A23E020D416}.Release|x64.ActiveCfg = Release|Any CPU + {4D1C70D7-FFD5-4518-A374-2A23E020D416}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2138,6 +2156,7 @@ Global {B4D6F6DC-0712-4F9F-A24F-6B76DAE84B6F} = {BE521908-7944-46F3-98BF-B47D34509934} {511C2FB0-9C73-4AC9-BA59-C8A84C089C59} = {18C8730C-0173-4987-9416-46F86EC20541} {EB52E451-9ED8-460E-9EE4-6717BFB12EAB} = {5D988C50-8E85-402A-9020-A4AB0565F0C9} + {4D1C70D7-FFD5-4518-A374-2A23E020D416} = {8AA78EE8-C33B-4BC5-992A-E5DE7AB0BEC7} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {1D43D91B-4F01-4A78-8250-CC6F9BD93A14} diff --git a/Core/Core/Logging/LoggingHelpers.cs b/Core/Core/Logging/LoggingHelpers.cs new file mode 100644 index 0000000000..b1520e0b1a --- /dev/null +++ b/Core/Core/Logging/LoggingHelpers.cs @@ -0,0 +1,16 @@ +using System; +using System.Diagnostics; + +namespace Speckle.Core.Logging; + +public static class LoggingHelpers +{ + private const long TicksPerMillisecond = 10000; + private const long TicksPerSecond = TicksPerMillisecond * 1000; + private static readonly double STickFrequency = (double)TicksPerSecond / Stopwatch.Frequency; + + public static TimeSpan GetElapsedTime(long startingTimestamp, long endingTimestamp) + { + return new TimeSpan((long)((endingTimestamp - startingTimestamp) * STickFrequency)); + } +} diff --git a/Core/Core/Models/DynamicBase.cs b/Core/Core/Models/DynamicBase.cs index 63955a2f05..2af9c5deb7 100644 --- a/Core/Core/Models/DynamicBase.cs +++ b/Core/Core/Models/DynamicBase.cs @@ -22,12 +22,6 @@ public class DynamicBase : DynamicObject, IDynamicMetaObjectProvider public const DynamicBaseMemberType DefaultIncludeMembers = DynamicBaseMemberType.Instance | DynamicBaseMemberType.Dynamic; - // Rule for multiple leading @. - private static Regex manyLeadingAtChars = new(@"^@{2,}"); - - // Rule for invalid chars. - private static Regex invalidChars = new(@"[\.\/]"); - private static Dictionary> propInfoCache = new(); /// @@ -114,41 +108,33 @@ public override bool TrySetMember(SetMemberBinder binder, object value) return valid; } + private static readonly HashSet DisallowedPropNameChars = new() { '.', '/' }; + public bool IsPropNameValid(string name, out string reason) { - // Existing members - //var members = GetInstanceMembersNames(); + if (string.IsNullOrEmpty(name) || name == "@") + { + reason = "Found empty prop name"; + return false; + } - // TODO: Check for detached/non-detached duplicate names? i.e: '@something' vs 'something' - // TODO: Instance members will not be overwritten, this may cause issues. - var checks = new List<(bool, string)> + if (name.StartsWith("@@")) { - (!(string.IsNullOrEmpty(name) || name == "@"), "Found empty prop name"), - // Checks for multiple leading @ - ( - !manyLeadingAtChars.IsMatch(name), - "Only one leading '@' char is allowed. This signals the property value should be detached." - ), - // Checks for invalid chars - ( - !invalidChars.IsMatch(name), - $"Prop with name '{name}' contains invalid characters. The following characters are not allowed: ./" - ) - // Checks if you are trying to change a member property - //(!members.Contains(name), "Modifying the value of instance member properties is not allowed.") - }; - - var r = ""; - // Prop name is valid if none of the checks are true - var isValid = checks.TrueForAll(v => + reason = "Only one leading '@' char is allowed. This signals the property value should be detached."; + return false; + } + + foreach (char c in name) { - if (!v.Item1) - r = v.Item2; - return v.Item1; - }); + if (DisallowedPropNameChars.Contains(c)) + { + reason = $"Prop with name '{name}' contains invalid characters. The following characters are not allowed: ./"; + return false; + } + } - reason = r; - return isValid; + reason = ""; + return true; } private static void PopulatePropInfoCache(Type type) diff --git a/Core/Core/Serialisation/BaseObjectDeserializerV2.cs b/Core/Core/Serialisation/BaseObjectDeserializerV2.cs index cf2d11979c..a063b37089 100644 --- a/Core/Core/Serialisation/BaseObjectDeserializerV2.cs +++ b/Core/Core/Serialisation/BaseObjectDeserializerV2.cs @@ -43,6 +43,9 @@ public sealed class BaseObjectDeserializerV2 public string? BlobStorageFolder { get; set; } public TimeSpan Elapsed { get; private set; } + public static int DefaultNumberThreads => Math.Min(Environment.ProcessorCount, 6); //6 threads seems the sweet spot, see performance test project + public int WorkerThreadCount { get; set; } = DefaultNumberThreads; + /// The JSON string of the object to be deserialized /// A typed object deserialized from the /// Thrown when @@ -59,7 +62,7 @@ public Base Deserialize(string rootObjectJson) _busy = true; var stopwatch = Stopwatch.StartNew(); _deserializedObjects = new(); - _workerThreads = new DeserializationWorkerThreads(this); + _workerThreads = new DeserializationWorkerThreads(this, WorkerThreadCount); _workerThreads.Start(); List<(string, int)> closures = GetClosures(rootObjectJson); @@ -144,9 +147,9 @@ public Base Deserialize(string rootObjectJson) reader.DateParseHandling = DateParseHandling.None; doc1 = JObject.Load(reader); } - + object? converted = ConvertJsonElement(doc1); - + lock (_callbackLock) OnProgressAction?.Invoke("DS", 1); return converted; @@ -208,9 +211,10 @@ public Base Deserialize(string rootObjectJson) return retList; case JTokenType.Object: - Dictionary dict = new(); + var jObject = (JContainer)doc; + Dictionary dict = new(jObject.Count); - foreach (JToken propJToken in doc) + foreach (JToken propJToken in jObject) { JProperty prop = (JProperty)propJToken; if (prop.Name == "__closure") @@ -221,9 +225,11 @@ public Base Deserialize(string rootObjectJson) if (!dict.ContainsKey(TypeDiscriminator)) return dict; - if (dict[TypeDiscriminator] as string == "reference" && dict.ContainsKey("referencedId")) + if ( + dict[TypeDiscriminator] as string == "reference" && dict.TryGetValue("referencedId", out object? referencedId) + ) { - var objId = (string)dict["referencedId"]!; + var objId = (string)referencedId!; object deserialized = null; lock (_deserializedObjects) if (_deserializedObjects.TryGetValue(objId, out object? o)) @@ -247,9 +253,10 @@ public Base Deserialize(string rootObjectJson) // This reference was not already deserialized. Do it now in sync mode string objectJson = ReadTransport.GetObject(objId); - if(objectJson is null) throw new Exception($"Failed to fetch object id {objId} from {ReadTransport} "); + if (objectJson is null) + throw new Exception($"Failed to fetch object id {objId} from {ReadTransport} "); deserialized = DeserializeTransportObject(objectJson); - + lock (_deserializedObjects) _deserializedObjects[objId] = deserialized; return deserialized; @@ -288,14 +295,12 @@ private Base Dict2Base(Dictionary dictObj) } Type targetValueType = property.PropertyType; - bool conversionOk = ValueConverter.ConvertValue(targetValueType, entry.Value, out object convertedValue); + bool conversionOk = ValueConverter.ConvertValue(targetValueType, entry.Value, out object? convertedValue); if (conversionOk) property.SetValue(baseObj, convertedValue); else // Cannot convert the value in the json to the static property type - throw new Exception( - $"Cannot deserialize {entry.Value.GetType().FullName} to {targetValueType.FullName}" - ); + throw new Exception($"Cannot deserialize {entry.Value.GetType().FullName} to {targetValueType.FullName}"); } else { diff --git a/Core/Core/Serialisation/DeserializationWorkerThreads.cs b/Core/Core/Serialisation/DeserializationWorkerThreads.cs index 3a25f38723..527b606504 100644 --- a/Core/Core/Serialisation/DeserializationWorkerThreads.cs +++ b/Core/Core/Serialisation/DeserializationWorkerThreads.cs @@ -18,10 +18,10 @@ internal sealed class DeserializationWorkerThreads : ParallelOperationExecutor Receive_FromSQLite() + { + Base? b = await Speckle.Core.Api.Operations + .Receive( + _dataSource.ObjectId, + null, + _dataSource.Transport, + onErrorAction: (message, ex) => throw new Exception(message, ex) + ) + .ConfigureAwait(false); + + Trace.Assert(b is not null); + return b; + } +} diff --git a/Core/TestsPerformance/Api/Operations/TraverseCommit.cs b/Core/TestsPerformance/Api/Operations/TraverseCommit.cs new file mode 100644 index 0000000000..d7bf251d66 --- /dev/null +++ b/Core/TestsPerformance/Api/Operations/TraverseCommit.cs @@ -0,0 +1,38 @@ +using BenchmarkDotNet.Attributes; +using Speckle.Core.Models; +using Speckle.Core.Models.GraphTraversal; + +namespace TestsPerformance.Api.Operations; + +[MemoryDiagnoser] +[RegressionTestConfig(1, 1, 20, nugetVersions:"2.15.2")] +public class TraverseCommit +{ + [Params(0, 4, 9, 19)] + public int DataComplexity { get; set; } + + private Base _testData; + private GraphTraversal _sut; + + [GlobalSetup] + public async Task Setup() + { + using var dataSource = new TestDataHelper(); + await dataSource.SeedTransport(DataComplexity).ConfigureAwait(false); + _testData = await dataSource.DeserializeBase().ConfigureAwait(false); + + var convertableRule = TraversalRule + .NewTraversalRule() + .When(b => b.speckle_type.Contains("Geometry")) + .When(DefaultTraversal.HasDisplayValue) + .ContinueTraversing(_ => DefaultTraversal.elementsPropAliases); + + _sut = new GraphTraversal(convertableRule, DefaultTraversal.DefaultRule); + } + + [Benchmark] + public List Traverse() + { + return _sut.Traverse(_testData).ToList(); + } +} diff --git a/Core/TestsPerformance/Program.cs b/Core/TestsPerformance/Program.cs new file mode 100644 index 0000000000..71913a106f --- /dev/null +++ b/Core/TestsPerformance/Program.cs @@ -0,0 +1,11 @@ +using BenchmarkDotNet.Running; + +namespace TestsPerformance; + +public static class Program +{ + public static async Task Main(string[] args) + { + BenchmarkSwitcher.FromAssemblies(new[] { typeof(Program).Assembly }).Run(args); + } +} diff --git a/Core/TestsPerformance/RegressionTestConfig.cs b/Core/TestsPerformance/RegressionTestConfig.cs new file mode 100644 index 0000000000..06e02982a8 --- /dev/null +++ b/Core/TestsPerformance/RegressionTestConfig.cs @@ -0,0 +1,55 @@ +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Engines; +using BenchmarkDotNet.Environments; +using BenchmarkDotNet.Jobs; + +namespace TestsPerformance; + +[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class, AllowMultiple = true)] +public sealed class RegressionTestConfigAttribute : Attribute, IConfigSource +{ + public IConfig Config { get; private set; } + + public RegressionTestConfigAttribute( + int launchCount = 1, + int warmupCount = 0, + int iterationCount = 10, + RunStrategy strategy = RunStrategy.Monitoring, + bool includeHead = true, + params string[] nugetVersions + ) + { + List jobs = new(); + + if (includeHead) + { + jobs.Add( + new Job("Head") + .WithRuntime(ClrRuntime.Net481) + .WithStrategy(strategy) + .WithLaunchCount(launchCount) + .WithWarmupCount(warmupCount) + .WithIterationCount(iterationCount) + ); + } + + bool isBaseline = true; + foreach (var version in nugetVersions) + { + jobs.Add( + new Job(version) + .WithRuntime(ClrRuntime.Net481) + .WithStrategy(strategy) + .WithLaunchCount(launchCount) + .WithWarmupCount(warmupCount) + .WithIterationCount(iterationCount) + .WithNuGet("Speckle.Objects", version) + .WithBaseline(isBaseline) + ); + + isBaseline = false; + } + + Config = ManualConfig.CreateEmpty().AddJob(jobs.ToArray()); + } +} diff --git a/Core/TestsPerformance/Serialisation/DeserializationWorkerThreads.cs b/Core/TestsPerformance/Serialisation/DeserializationWorkerThreads.cs new file mode 100644 index 0000000000..931a6e243e --- /dev/null +++ b/Core/TestsPerformance/Serialisation/DeserializationWorkerThreads.cs @@ -0,0 +1,39 @@ +using BenchmarkDotNet.Attributes; +using Speckle.Core.Models; +using Speckle.Core.Serialisation; +using Speckle.Core.Transports; + +namespace TestsPerformance.Serialisation; + +[MemoryDiagnoser] +[RegressionTestConfig(1, 1, 6)] +public class DeserializationWorkerThreads +{ + public static IEnumerable NumThreadsToTest => Enumerable.Range(0, Environment.ProcessorCount + 1); + + [Params(0, 9)] + public int DataComplexity { get; set; } + + private TestDataHelper _dataSource; + + [GlobalSetup] + public async Task Setup() + { + _dataSource = new TestDataHelper(); + await _dataSource.SeedTransport(DataComplexity).ConfigureAwait(false); + } + + [GlobalCleanup] + public virtual void Cleanup() + { + _dataSource.Dispose(); + } + + [Benchmark] + [ArgumentsSource(nameof(NumThreadsToTest))] + public Base RunTest(int numThreads) + { + BaseObjectDeserializerV2 sut = new() { WorkerThreadCount = numThreads, ReadTransport = _dataSource.Transport }; + return sut.Deserialize(_dataSource.Transport.GetObject(_dataSource.ObjectId)!); + } +} diff --git a/Core/TestsPerformance/TestDataHelper.cs b/Core/TestsPerformance/TestDataHelper.cs new file mode 100644 index 0000000000..44a99b68f8 --- /dev/null +++ b/Core/TestsPerformance/TestDataHelper.cs @@ -0,0 +1,57 @@ +using System.Reactive; +using Microsoft.Data.Sqlite; +using Speckle.Core.Api; +using Speckle.Core.Credentials; +using Speckle.Core.Models; +using Speckle.Core.Transports; + +namespace TestsPerformance; + +public sealed class TestDataHelper : IDisposable +{ + private static readonly string BasePath = $"./temp {Guid.NewGuid()}"; + private const string ApplicationName = "Speckle Performance Tests"; + + public SQLiteTransport Transport { get; private set; } + public string ObjectId { get; private set; } + + public async Task SeedTransport(int dataComplexity) + { + Transport = new SQLiteTransport(BasePath, ApplicationName); + + //seed SQLite transport with test data + ObjectId = await SeedTransport(dataComplexity, Transport).ConfigureAwait(false); + } + + public static async Task SeedTransport(int dataComplexity, ITransport transport) + { + //seed SQLite transport with test data + StreamWrapper sw = new($"https://latest.speckle.dev/streams/efd2c6a31d/branches/{dataComplexity}"); + var acc = await sw.GetAccount().ConfigureAwait(false); + using var client = new Client(acc); + var branch = await client.BranchGet(sw.StreamId, sw.BranchName!, 1).ConfigureAwait(false); + var objectId = branch.commits.items[0].referencedObject; + + using ServerTransport remoteTransport = new(acc, sw.StreamId); + transport.BeginWrite(); + await remoteTransport.CopyObjectAndChildren(objectId, transport).ConfigureAwait(false); + transport.EndWrite(); + await transport.WriteComplete().ConfigureAwait(false); + + return objectId; + } + + public async Task DeserializeBase() + { + return await Speckle.Core.Api.Operations + .Receive(ObjectId, null, Transport, onErrorAction: (message, ex) => throw new Exception(message, ex)) + .ConfigureAwait(false) ?? throw new InvalidOperationException("Test data was null"); + } + + public void Dispose() + { + Transport.Dispose(); + SqliteConnection.ClearAllPools(); + Directory.Delete(BasePath, true); + } +} diff --git a/Core/TestsPerformance/TestsPerformance.csproj b/Core/TestsPerformance/TestsPerformance.csproj new file mode 100644 index 0000000000..699be9dd4a --- /dev/null +++ b/Core/TestsPerformance/TestsPerformance.csproj @@ -0,0 +1,18 @@ + + + + net481 + enable + enable + exe + + + + + + + + + + + From dfc8ac58c18c32c0ccf7ca11cd0bc9b4ecbdc233 Mon Sep 17 00:00:00 2001 From: KatKatKateryna <89912278+KatKatKateryna@users.noreply.github.com> Date: Mon, 4 Sep 2023 15:23:06 +0100 Subject: [PATCH 25/57] feat(revit): receive GIS topography (#2875) * debugger setup * receiving Topography (breaking on Topography.Create) * clean mesh before passing to RevitGeometry * remove launch settings * move function to Gis class instead of all Topography * move duplicateXYpts check directly to Revit Topography * _remove extra reference * make a duplicate check optional (so in non-GIS case, the conversion will default to DirectShape in case of duplicates) * add Gis Topography to the general Topography class; clean the duplicate XY points for every incoming topography * GisTopographyclass to native namespace; assign other class properties This reverts commit 65ba9f56005d189babd3fe94e136abe225fecd88. --- .../ConverterRevitShared/ConverterRevit.cs | 1 + .../Partial Classes/ConvertTopography.cs | 12 ++++++++--- Objects/Objects/GIS/GisTopography.cs | 21 +++++++++++++++++++ 3 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 Objects/Objects/GIS/GisTopography.cs diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/ConverterRevit.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/ConverterRevit.cs index 188bcb3a74..a622062f36 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/ConverterRevit.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/ConverterRevit.cs @@ -698,6 +698,7 @@ public object ConvertToNativeObject(Base @object) case PolygonElement o: return PolygonElementToNative(o); + //hacky but the current comments camera is not a Base object //used only from DUI and not for normal geometry conversion case Base b: diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertTopography.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertTopography.cs index 61a0b44014..067341313d 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertTopography.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertTopography.cs @@ -20,6 +20,7 @@ public ApplicationObject TopographyToNative(Topography speckleSurface) return appObj; var pts = new List(); + var pointTuplesList = new List<(double, double)>(); var facets = new List(); foreach (Geometry.Mesh displayMesh in speckleSurface.displayValue) { @@ -29,10 +30,15 @@ public ApplicationObject TopographyToNative(Topography speckleSurface) pts.Capacity += displayMesh.vertices.Count / 3; for (int i = 0; i < displayMesh.vertices.Count; i += 3) { - var point = new Geometry.Point(displayMesh.vertices[i], displayMesh.vertices[i + 1], displayMesh.vertices[i + 2], displayMesh.units); - pts.Add(PointToNative(point)); + // add a check for duplicate points, if 'keepXYDuplicates' is false + var ptTuple = (displayMesh.vertices[i], displayMesh.vertices[i + 1]); + if (!pointTuplesList.Contains(ptTuple)) + { + pointTuplesList.Add(ptTuple); + var point = new Geometry.Point(displayMesh.vertices[i], displayMesh.vertices[i + 1], displayMesh.vertices[i + 2], displayMesh.units); + pts.Add(PointToNative(point)); + } } - } if (docObj != null) diff --git a/Objects/Objects/GIS/GisTopography.cs b/Objects/Objects/GIS/GisTopography.cs new file mode 100644 index 0000000000..0b05a4719c --- /dev/null +++ b/Objects/Objects/GIS/GisTopography.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using Objects.BuiltElements; +using Objects.BuiltElements.Revit; +using Objects.Geometry; +using Speckle.Core.Models; + +namespace Objects.GIS +{ + public class GisTopography : Topography + { + public int band_count { get; set; } + public List band_names { get; set; } + public float x_origin { get; set; } + public float y_origin { get; set; } + public int x_size { get; set; } + public int y_size { get; set; } + public float x_resolution { get; set; } + public float y_resolution { get; set; } + public List noDataValue { get; set; } + } +} From dfad1337d9b4f5ca925da8ba82cd3d7a369f610e Mon Sep 17 00:00:00 2001 From: Jonathon Broughton Date: Mon, 4 Sep 2023 18:06:06 +0100 Subject: [PATCH 26/57] feat(Revit): Support MEP zones (#2868) * feat: Add Zone class with constructors and properties This commit adds a new `Zone` class to the codebase. The `Zone` class represents a space in a building and includes properties such as name, number, base point, level, top level, offsets, voids, outline, space type, zone name, units, display value (as a list of meshes), area, and volume. The `Zone` class has two constructors: one with basic parameters (name, number, base point, level) and another with additional parameters (top level and offsets). These constructors allow for the creation of `Zone` objects with different levels of detail. These changes enhance the functionality of the codebase by providing support for working with zones in building models. * feat: Add Zone object to Space class This commit adds a new property, `zone`, to the `Space` class in order to improve forward compatibility. The existing property `zoneName` is retained for backwards compatibility. Additionally, some import statements were added at the top of the file. Changes: - Added property `Zone zone` - Retained property `string zoneName` * feat: Add Zone class and conversion methods This commit adds the `Zone` class to the `Objects.BuiltElements.Revit` namespace. The `Zone` class represents a collection of one or more spaces in Revit. It has properties such as name, level, service type, isDefault, units, area, volume, and perimeter. The commit also includes conversion methods in the `ConverterRevit` class for converting a Speckle zone to a native Revit zone (`ZoneToNative`) and vice versa (`ZoneToSpeckle`). Additionally, there is a method (`ConvertAndCacheZone`) for converting and caching a Revit zone. * feat: Add validation and element retrieval for selected objects This commit adds a method to validate the selected objects and retrieve the corresponding elements. For zones, it retrieves the spaces within the zone. The method takes a list of elements as input and returns a list of valid elements, including any interpreted selections such as spaces from zones. Duplicate elements are removed. The `ValidateSelectedObjects` method handles the resolution of selected elements to their convertible states. It uses a switch statement to handle different types of elements. For zones, it iterates through the spaces within the zone and adds them to a list. For other types of elements, it simply returns the element itself. These changes improve the accuracy and reliability of selecting and retrieving elements in the ConnectorRevit UI module. * feat: Convert and cache zone for Revit space This commit adds functionality to convert and cache the zone for a Revit space. Previously, zones were only stored as a name string property of the space. This change ensures that the zone is properly converted and cached during the conversion process. * feat: Add caching for converted Zones This commit adds caching functionality to improve performance when converting Zone definitions in the Revit converter. A new `Zones` dictionary is introduced to cache already converted Zones. This change aims to reduce redundant conversions and enhance overall efficiency. * selected object validation The code changes in this commit refactor the selected object validation process. The `ValidateSelectedObjects` method is introduced to validate the selected objects before assigning them to `state.SelectedObjectIds`. This ensures that only unique and valid objects are included in the list. * Refactor element validation and transformation logic The commit refactors the code responsible for validating and transforming a collection of elements. It renames the internal method, `ResolveElementsByType`, which handles specific validations and transformations based on the element type. The current implementation expands zones into their constituent spaces. Additional validations can be added in the future. * Change from linq to yield pattern * Refactor code for selecting spaces from a zone The code was refactored to simplify the selection of spaces from a zone. The method `GetSpacesFromZone` was removed and replaced with a direct iteration over the `zone.Spaces` collection. This change improves readability and eliminates unnecessary complexity in the code. * Refactor caching of converted Zones and improve code readability - Remove the `Zones` property from the `ConverterRevit` class. - Modify the `ConvertSpace` class to use a cache provided by `revitDocumentAggregateCache` for converting and caching Zones. - Remove the private method `ConvertAndCacheZone` from the `ConvertZone` class. These changes refactor how Zones are cached during conversion, improving performance and code organization. * Remove zoneName property * Add phase property to Zone class This commit adds a new property called "phase" to the Zone class in the Objects/Objects/BuiltElements/Zone.cs file. The phase property is of type string and allows for storing information about the phase of the zone. * Add roomId and phase properties to Space class - Added roomId property of type string to Space class - Added phase property of type string to Space class * add phase information * added Space properties - Refactored the code to use object initialization syntax for creating a new `speckleSpace` object. - Added `area`, `volume`, `spaceType`, and `displayValue` properties to the `speckleSpace` object. - Updated the logic to assign values to the `voids` property of the `speckleSpace` object when there are more than one profile. - Added support for associating a Room with a Space by storing its ID in the `"roomId"` property of the `speckleSpace` object. - Updated the logic to convert and assign values to the `zone` property of the `speckleSpace` object using cached data from Revit documents. - Implemented special handling for retrieving and assigning Phase information as a parameter on the Space object. These changes improve code readability, add additional properties to capture relevant data, and enhance functionality related to Rooms, Zones, and Phases. * lint and format * Refactor Zone class and add RevitZone subclass - Added a new RevitZone subclass in the Objects.BuiltElements.Revit namespace - Removed unused properties from the Zone class - Added new properties to the RevitZone class: level, phaseName, parameters, elementId, isDefault, serviceType * Refactor ConvertZone class to use RevitZone instead of Zone - Rename ZoneToSpeckle method parameter from DB.Zone to RevitZone - Rename Zone class to RevitZone - Update speckleZone["phase"] key to "phaseName" * refactor usings - Remove unused using statements - Reorder using statements alphabetically - Add missing using statement for Autodesk.Revit.DB.Mechanical - Remove unused namespace alias "Point" * Refactor ConvertZone.cs: improve zone conversion logic This commit refactors the `ZoneToNative` method in the `ConvertZone.cs` file to improve the logic for converting zones. The changes include: - Removing unnecessary using statements - Adding a new method, `ConvertZoneToRevit`, to handle the actual conversion process - Updating the `ZoneToNative` method to use the new conversion method and update the application object with relevant information - Implementing logic to check if a zone with the same name already exists in the document and updating it accordingly - Handling phase-related operations, including checking if a target phase exists and creating a default phase if not found - Setting instance parameters for existing zones or creating new ones as needed These changes aim to enhance the accuracy and efficiency of zone conversions in Revit. * Refactor Space class properties for better clarity and compatibility - Change the type of the "zone" property to RevitZone - Rename the "phase" property to "phaseName" - No other significant changes made in this commit * Refactor ConvertSpace.cs to use RevitZone instead of Zone - Update the type of cache from Zone to RevitZone - Replace references to Zone with RevitZone in GetOrInitializeEmptyCacheOfType method - Update the conversion logic for revitSpace.Zone * introduce nullable reference type for RevitZone property, add Obsolete attribute to zoneName property - Add using statement for System namespace - Change RevitZone property to be nullable - Add Obsolete attribute to zoneName property * SpaceToSpeckle tidy up * refactored out handler methods from the single conversion method This commit adds logic to handle the creation and modification of Revit spaces based on Speckle space objects. Significant changes include: - Determining the target phase for the space - Creating a new Revit space if none exists or if it is unplaced - Setting the type, limits, and instance parameters of the Revit space The commit also includes helper methods to create new spaces and determine the target phase. * refactor out handler methods from the single conversion method. This commit adds support for creating and updating Revit zones. Significant changes include: - Implemented logic to determine the target phase for a zone - Created a method to create a new Revit zone if it doesn't exist or update an existing one - Handled setting instance parameters for the zone - Updated the speckleZone object with the level information from the Revit zone * Update ConvertSpace.cs to change the status from "Unknown" to "Skipped" when the target phase is not selected in the Active View. * Rename `ValidateSelectedObjects` to `HandleSelectedObjectDescendants` --- .../UI/ConnectorBindingsRevit.Selection.cs | 30 ++ .../UI/ConnectorBindingsRevit.Send.cs | 3 +- .../ConverterRevitShared.projitems | 1 + .../Partial Classes/ConvertSpace.cs | 292 ++++++++++++++---- .../Partial Classes/ConvertZone.cs | 185 +++++++++++ Objects/Objects/BuiltElements/Space.cs | 14 +- Objects/Objects/BuiltElements/Zone.cs | 42 +++ 7 files changed, 499 insertions(+), 68 deletions(-) create mode 100644 Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertZone.cs create mode 100644 Objects/Objects/BuiltElements/Zone.cs diff --git a/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Selection.cs b/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Selection.cs index 88ead97ecb..6d8cd32413 100644 --- a/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Selection.cs +++ b/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Selection.cs @@ -583,5 +583,35 @@ private static string GetStringValue(Parameter p) return p.AsString().ToLowerInvariant(); } } + + /// Processes the provided list of elements, applying specific validations and transformations based on the element type. + /// + /// A collection of elements to process. + /// + /// A collection of elements after applying the respective validations and transformations. + /// + /// + /// Current Validations and Transformations: + /// - Zones are expanded into their constituent spaces. + /// - [Add additional validations here as they are implemented.] + /// + private static IEnumerable HandleSelectedObjectDescendants(IEnumerable selectedObjects) + { + // Handle the resolution of selected Elements to their convertable states here + foreach (var element in selectedObjects) + { + switch (element) + { + case Autodesk.Revit.DB.Mechanical.Zone zone: + foreach (var space in zone.Spaces.OfType()) + yield return space; + break; + + default: + yield return element; + break; + } + } + } } } diff --git a/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Send.cs b/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Send.cs index 3f55612e49..cb25552a54 100644 --- a/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Send.cs +++ b/ConnectorRevit/ConnectorRevit/UI/ConnectorBindingsRevit.Send.cs @@ -52,7 +52,8 @@ public override async Task SendStream(StreamState state, ProgressViewMod var client = state.Client; var selectedObjects = GetSelectionFilterObjects(converter, state.Filter); - state.SelectedObjectIds = selectedObjects.Select(x => x.UniqueId).ToList(); + selectedObjects = HandleSelectedObjectDescendants(selectedObjects).ToList(); + state.SelectedObjectIds = selectedObjects.Select(x => x.UniqueId).Distinct().ToList(); if (!selectedObjects.Any()) throw new InvalidOperationException( diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/ConverterRevitShared.projitems b/Objects/Converters/ConverterRevit/ConverterRevitShared/ConverterRevitShared.projitems index 3982533125..a4d1302898 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/ConverterRevitShared.projitems +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/ConverterRevitShared.projitems @@ -59,6 +59,7 @@ + diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertSpace.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertSpace.cs index 196466958e..5b9485f137 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertSpace.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertSpace.cs @@ -1,10 +1,12 @@ -using Autodesk.Revit.DB; -using Objects.BuiltElements; -using Speckle.Core.Models; using System; using System.Collections.Generic; using System.Linq; +using Autodesk.Revit.DB; +using Objects.BuiltElements; +using Objects.BuiltElements.Revit; +using Speckle.Core.Models; using DB = Autodesk.Revit.DB.Mechanical; +using Level = Autodesk.Revit.DB.Level; using Point = Objects.Geometry.Point; namespace Objects.Converter.Revit @@ -13,101 +15,261 @@ public partial class ConverterRevit { public ApplicationObject SpaceToNative(Space speckleSpace) { + var appObj = new ApplicationObject(speckleSpace.id, speckleSpace.speckle_type) + { + applicationId = speckleSpace.applicationId + }; + var revitSpace = GetExistingElementByApplicationId(speckleSpace.applicationId) as DB.Space; - var appObj = new ApplicationObject(speckleSpace.id, speckleSpace.speckle_type) { applicationId = speckleSpace.applicationId }; - + // skip if element already exists in doc & receive mode is set to ignore if (IsIgnore(revitSpace, appObj)) return appObj; - var levelState = ApplicationObject.State.Unknown; - var level = ConvertLevelToRevit(speckleSpace.level, out levelState); + // Determine Space Location + var level = ConvertLevelToRevit(speckleSpace.level, out _); var basePoint = PointToNative(speckleSpace.basePoint); - var upperLimit = ConvertLevelToRevit(speckleSpace.topLevel, out levelState); - // create new space if none existing, include zone information if available + var upperLimit = ConvertLevelToRevit(speckleSpace.topLevel, out _); + + // no target phase found, it will use the active view phase anyway + var targetPhase = DetermineTargetPhase(speckleSpace, revitSpace); + var activeViewPhase = DetermineActiveViewPhase(); + + // even though the API documented to allow for spaces to be created in any phase, + // it is not possible to create on any phase but the Active View phase + var activeViewPhaseName = activeViewPhase?.Name; + var targetPhaseName = targetPhase?.Name; + + if (activeViewPhase.Id != targetPhase.Id) + { + appObj.Update( + logItem: $"Space Phase {targetPhase.Name} not selected in the Active View.", + status: ApplicationObject.State.Skipped + ); + return appObj; + // return null; + } + + + revitSpace = CreateRevitSpaceIfNeeded(speckleSpace, revitSpace, targetPhase, level, basePoint); + if (revitSpace == null) { - revitSpace = Doc.Create.NewSpace(level, new UV(basePoint.X, basePoint.Y)); - if (speckleSpace.zoneName != null) - { - var speckleZone = new FilteredElementCollector(Doc).OfClass(typeof(DB.Zone)).Cast().Where(z => z.Name == speckleSpace.zoneName).FirstOrDefault(); - if (speckleZone != null) // else space is added to default zone - { - var spaceSet = new DB.SpaceSet(); - spaceSet.Insert(revitSpace); - speckleZone.AddSpaces(spaceSet); - } - } + appObj.Update(status: ApplicationObject.State.Failed); + return appObj; } - else + + var revitZone = revitSpace.Zone; + revitZone = CreateRevitZoneIfNeeded(speckleSpace, revitZone, targetPhase, level); + + // if a relevant zone exists add space to it + if (revitZone != null) { - // unplaced space, so just delete and recreate from scratch (but make sure to copy host zone info) - // ideally we just place/move the existing space instead, but this seems like a real pain to do with the revit api? should update in future! - if (revitSpace.Area == 0 && revitSpace.Location == null) + var currentSpaces = revitZone.Spaces; + + // if the space is already in the zone, do nothing. + if (!currentSpaces.Contains(revitSpace)) { - var zone = revitSpace.Zone; - Doc.Delete(revitSpace.Id); - revitSpace = Doc.Create.NewSpace(level, new UV(basePoint.X, basePoint.Y)); - if (zone != null) - { - var spaceSet = new DB.SpaceSet(); - spaceSet.Insert(revitSpace); - zone.AddSpaces(spaceSet); - } + var spaceSet = new DB.SpaceSet(); + spaceSet.Insert(revitSpace); + revitZone.AddSpaces(spaceSet); } } revitSpace.Name = speckleSpace.name; revitSpace.Number = speckleSpace.number; - // if user does not specify an UpperLimit level, assume use of default UpperLimit (current level) and default offset values (base offset of 0, limit offset is distance to next level above) - if (upperLimit != null) - { - TrySetParam(revitSpace, BuiltInParameter.ROOM_UPPER_LEVEL, upperLimit); // not sure why, don't actually seem to be able to set the UpperLimit property when UpperLimit is null and UpperLimit level to be provided is the same as the Level level - it stays null after assignment! - revitSpace.LimitOffset = ScaleToNative(speckleSpace.topOffset, speckleSpace.units); - revitSpace.BaseOffset = ScaleToNative(speckleSpace.baseOffset, speckleSpace.units); - } + SetSpaceLimits(speckleSpace, upperLimit, revitSpace); + SetSpaceType(speckleSpace, revitSpace); + SetInstanceParameters(revitSpace, speckleSpace); + + appObj.Update(status: ApplicationObject.State.Created, createdId: revitSpace.UniqueId, convertedItem: revitSpace); + return appObj; + } + + /// + /// Sets the type of the Revit space based on the provided Speckle space. + /// + /// The Space object from the Speckle system. + /// The Revit Space object to update. + /// + /// The method will try to set the Revit space type based on the Speckle space type. + /// If the Speckle space type is not recognized, it will default to 'NoSpaceType'. + /// + private static void SetSpaceType(Space speckleSpace, DB.Space revitSpace) + { + if (string.IsNullOrEmpty(speckleSpace.spaceType)) + return; + revitSpace.SpaceType = Enum.TryParse(speckleSpace.spaceType, out DB.SpaceType spaceType) + ? spaceType + : DB.SpaceType.NoSpaceType; + } + + /// + /// Sets the upper limit and offsets for a Revit space based on a Speckle space. + /// + /// The Speckle Space object containing the upper limit and offset information. + /// The Element object representing the upper limit level. + /// The Revit Space object to be updated. + /// + /// The method will attempt to set the upper limit and offsets for the Revit space. + /// If an upper limit is not specified, the method will return early without making changes. + /// + private void SetSpaceLimits(Space speckleSpace, Element upperLimit, DB.Space revitSpace) + { + if (upperLimit == null) + return; + + TrySetParam(revitSpace, BuiltInParameter.ROOM_UPPER_LEVEL, upperLimit); - if (!string.IsNullOrEmpty(speckleSpace.spaceType)) + revitSpace.LimitOffset = ScaleToNative(speckleSpace.topOffset, speckleSpace.units); + revitSpace.BaseOffset = ScaleToNative(speckleSpace.baseOffset, speckleSpace.units); + } + + /// + /// Handles the Revit Space based on the provided Speckle Space and target phase. + /// + /// The Space object from the Speckle system. + /// The existing Revit Space object. This may be modified by the method. + /// The target Phase object. + /// The Level object associated with the space. + /// The base point for the space. + /// The modified or newly created Revit Space object. + private static DB.Space CreateRevitSpaceIfNeeded( + Space speckleSpace, + DB.Space revitSpace, + Phase targetPhase, + Level level, + XYZ basePoint + ) + { + // Main logic + if (revitSpace == null) { - try - { - revitSpace.SpaceType = (DB.SpaceType)Enum.Parse(typeof(DB.SpaceType), speckleSpace.spaceType); - } - catch + revitSpace = CreateNewSpace(level, targetPhase, new UV(basePoint.X, basePoint.Y), speckleSpace.level.name); + + if (revitSpace == null) { - revitSpace.SpaceType = DB.SpaceType.NoSpaceType; + return null; } } + else if (revitSpace.Area == 0 && revitSpace.Location == null) + { + Doc.Delete(revitSpace.Id); - SetInstanceParameters(revitSpace, speckleSpace); - appObj.Update(status: ApplicationObject.State.Created, createdId: revitSpace.UniqueId, convertedItem: revitSpace); - return appObj; + revitSpace = CreateNewSpace(level, targetPhase, new UV(basePoint.X, basePoint.Y), speckleSpace.level.name); + } + + return revitSpace; + } + + /// + /// Creates a new RevitSpace based on the provided parameters. + /// + /// The level for the new space. + /// The target phase for the new space. If null, the active view phase will be used. + /// The base point (UV coordinates) for the new space. + /// The name of the level. Used to determine if the space has a location. + /// The newly created RevitSpace, or null if the space could not be created. + private static DB.Space CreateNewSpace(Level level, Phase targetPhase, UV basePoint, string levelName) + { + if (targetPhase == null) + { + return string.IsNullOrEmpty(levelName) || basePoint == null + ? null + : Doc.Create.NewSpace(level, new UV(basePoint.U, basePoint.V)); + } + + return string.IsNullOrEmpty(levelName) // has no location + ? Doc.Create.NewSpace(targetPhase) + : Doc.Create.NewSpace(level, targetPhase, new UV(basePoint.U, basePoint.V)); } - public BuiltElements.Space SpaceToSpeckle(DB.Space revitSpace) + /// + /// Determines the target phase for a space based on available information. + /// + /// The Space object from the Speckle system. + /// The Space object from the Revit system. + /// The determined target Phase object. + /// + /// The method tries to determine the target phase based on the following priority: + /// 1. Phase from the Speckle space (if it exists). + /// 2. Phase from the existing Revit space (if it exists). + /// 3. Phase from the active view in Revit. + /// + private static Phase DetermineTargetPhase(Space speckleSpace, DB.Space revitSpace) + { + // Get all phases + var phases = Doc.Phases.Cast().ToList(); + + // Determine existing space phase, if any + Phase existingSpacePhase = null; + if (revitSpace != null) + { + string existingSpacePhaseName = revitSpace.get_Parameter(BuiltInParameter.ROOM_PHASE).AsValueString(); + existingSpacePhase = phases.FirstOrDefault(x => x.Name == existingSpacePhaseName); + } + + // Determine target phase + // Priority: speckleSpace phase > existing space phase > active view phase + string targetPhaseName = speckleSpace.phaseName; + var targetPhase = phases.FirstOrDefault(x => x.Name == targetPhaseName) ?? existingSpacePhase; + + return targetPhase; + } + + private static Phase DetermineActiveViewPhase(IEnumerable phases = null) + { + phases ??= Doc.Phases.Cast(); + + // Determine active view phase + var activeViewPhaseName = Doc.ActiveView.get_Parameter(BuiltInParameter.VIEW_PHASE).AsValueString(); + return phases.FirstOrDefault(x => x.Name == activeViewPhaseName); + } + + public Space SpaceToSpeckle(DB.Space revitSpace) { var profiles = GetProfiles(revitSpace); - var speckleSpace = new Space(); - speckleSpace.name = revitSpace.Name; - speckleSpace.number = revitSpace.Number; - speckleSpace.basePoint = (Point)LocationToSpeckle(revitSpace); - speckleSpace.level = ConvertAndCacheLevel(revitSpace.LevelId, revitSpace.Document); - speckleSpace.topLevel = ConvertAndCacheLevel(revitSpace.get_Parameter(BuiltInParameter.ROOM_UPPER_LEVEL).AsElementId(), revitSpace.Document); - speckleSpace.baseOffset = GetParamValue(revitSpace, BuiltInParameter.ROOM_LOWER_OFFSET); - speckleSpace.topOffset = GetParamValue(revitSpace, BuiltInParameter.ROOM_UPPER_OFFSET); - speckleSpace.outline = profiles.Count != 0 ? profiles[0] : null; + var speckleSpace = new Space + { + name = revitSpace.Name, + number = revitSpace.Number, + basePoint = (Point)LocationToSpeckle(revitSpace), + level = ConvertAndCacheLevel(revitSpace.LevelId, revitSpace.Document), + topLevel = ConvertAndCacheLevel( + revitSpace.get_Parameter(BuiltInParameter.ROOM_UPPER_LEVEL).AsElementId(), + revitSpace.Document + ), + baseOffset = GetParamValue(revitSpace, BuiltInParameter.ROOM_LOWER_OFFSET), + topOffset = GetParamValue(revitSpace, BuiltInParameter.ROOM_UPPER_OFFSET), + outline = profiles.Count != 0 ? profiles[0] : null, + area = GetParamValue(revitSpace, BuiltInParameter.ROOM_AREA), + volume = GetParamValue(revitSpace, BuiltInParameter.ROOM_VOLUME), + spaceType = revitSpace.SpaceType.ToString(), + displayValue = GetElementDisplayValue(revitSpace) + }; + if (profiles.Count > 1) speckleSpace.voids = profiles.Skip(1).ToList(); - speckleSpace.area = GetParamValue(revitSpace, BuiltInParameter.ROOM_AREA); - speckleSpace.volume = GetParamValue(revitSpace, BuiltInParameter.ROOM_VOLUME); - speckleSpace.spaceType = revitSpace.SpaceType.ToString(); - speckleSpace.zoneName = revitSpace.Zone?.Name; + + // Spaces are typically associated with a Room, but not always + if (revitSpace.Room != null) + speckleSpace["roomId"] = revitSpace.Room.Id.ToString(); + + // Zones are stored as a Space prop despite being a parent object, so we need to convert it here + speckleSpace.zone = revitDocumentAggregateCache + .GetOrInitializeEmptyCacheOfType(out _) + .GetOrAdd(revitSpace.Zone.Name, () => ZoneToSpeckle(revitSpace.Zone), out _); GetAllRevitParamsAndIds(speckleSpace, revitSpace); - speckleSpace.displayValue = GetElementDisplayValue(revitSpace); + // Special Phase handling for Spaces, phase is found as a parameter on the Space object, not a property + var phase = Doc.GetElement(revitSpace.get_Parameter(BuiltInParameter.ROOM_PHASE).AsElementId()); + if (phase != null) + { + speckleSpace.phaseName = phase.Name; + } return speckleSpace; } diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertZone.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertZone.cs new file mode 100644 index 0000000000..cff488b8e6 --- /dev/null +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertZone.cs @@ -0,0 +1,185 @@ +using System; +using System.Linq; +using Autodesk.Revit.DB; +using Objects.BuiltElements; +using Objects.BuiltElements.Revit; +using Speckle.Core.Models; +using DB = Autodesk.Revit.DB.Mechanical; +using Level = Autodesk.Revit.DB.Level; + +namespace Objects.Converter.Revit +{ + public partial class ConverterRevit + { + public ApplicationObject ZoneToNative(RevitZone speckleZone) + { + var revitZone = ConvertZoneToRevit(speckleZone, out ApplicationObject.State state); + var appObj = new ApplicationObject(speckleZone.id, speckleZone.speckle_type) + { + applicationId = speckleZone.applicationId + }; + appObj.Update(status: state, createdId: revitZone.UniqueId, convertedItem: revitZone); + return appObj; + } + + public DB.Zone ConvertZoneToRevit(RevitZone speckleZone, out ApplicationObject.State state) + { + state = ApplicationObject.State.Unknown; + + if (speckleZone == null) + return null; + + var revitZone = GetExistingZone(speckleZone); + + var targetPhase = DetermineTargetPhase(speckleZone, revitZone); + + var revitZoneLevel = ConvertLevelToRevit(speckleZone.level, out _); + if (revitZone == null) + { + DB.Zone sameNameZone = GetZoneWithSameName(speckleZone); + + if (sameNameZone != null && sameNameZone.Phase.Name == speckleZone.phaseName) + { + revitZone = sameNameZone; + } + else + { + if (sameNameZone != null) + { + Doc.Delete(sameNameZone.Id); + Doc.Regenerate(); + } + + revitZone = CreateRevitZone(revitZoneLevel, targetPhase, speckleZone.name); + } + } + else + { + revitZone.Name = speckleZone.name; + } + + SetInstanceParameters(revitZone, speckleZone); + + var getSameNameZone = GetZoneWithSameName(speckleZone); + + state = ApplicationObject.State.Updated; + + return revitZone; + } + + private DB.Zone GetExistingZone(RevitZone speckleZone) + { + return GetExistingElementByApplicationId(speckleZone.applicationId) as DB.Zone; + } + + private DB.Zone GetZoneWithSameName(RevitZone speckleZone) + { + var docZones = new FilteredElementCollector(Doc).OfClass(typeof(DB.Zone)).ToElements().Cast(); + + return docZones.FirstOrDefault(x => x.Name == speckleZone.name); + } + + public static DB.Zone CreateRevitZone(Level revitZoneLevel, Phase targetPhase, string zoneName = null) + { + var newZone = Doc.Create.NewZone(revitZoneLevel, targetPhase); + + if (zoneName != null) + newZone.Name = zoneName; + Doc.Regenerate(); + return newZone; + } + + /// + /// Determines the target phase for a space based on available information. + /// + /// The Space object from the Speckle system. + /// The Space object from the Revit system. + /// The determined target Phase object. + /// + /// The method tries to determine the target phase based on the following priority: + /// 1. Phase from the Speckle space (if it exists). + /// 2. Phase from the existing Revit space (if it exists). + /// 3. Phase from the active view in Revit. + /// + private static Phase DetermineTargetPhase(RevitZone speckleZone, DB.Zone revitZone) + { + // Get all phases + var phases = Doc.Phases.Cast(); + + // Determine existing space phase, if any + Phase existingZonePhase = null; + if (revitZone != null) + { + string existingZonePhaseName = revitZone.Phase.ToString(); + existingZonePhase = phases.FirstOrDefault(x => x.Name == existingZonePhaseName); + } + + // Determine active view phase + string activeViewPhaseName = Doc.ActiveView.get_Parameter(BuiltInParameter.VIEW_PHASE).AsValueString(); + var activeViewPhase = phases.FirstOrDefault(x => x.Name == activeViewPhaseName); + + // Determine target phase + // Priority: speckleSpace phase > existing space phase > active view phase + string targetPhaseName = speckleZone.phaseName; + var targetPhase = phases.FirstOrDefault(x => x.Name == targetPhaseName) ?? existingZonePhase ?? activeViewPhase; + + return targetPhase; + } + + /// + /// Handles the Revit Zone based on the provided Speckle Space and target phase. + /// + /// The Space object from the Speckle system. + /// The existing Revit Zone object. This may be modified by the method. + /// The target Phase object. + /// The Level object associated with the space. + /// The modified or newly created Revit Zone object. + private DB.Zone CreateRevitZoneIfNeeded(Space speckleSpace, DB.Zone revitZone, Phase targetPhase, Level level) + { + var zoneName = speckleSpace.zone != null ? speckleSpace.zone.name : speckleSpace.zoneName; // zoneName is the previous property retained here for backwards compatibility. + + if (revitZone == null && !string.IsNullOrEmpty(zoneName)) + { + revitZone = ConvertZoneToRevit(speckleSpace.zone, out _); + } + else if (revitZone != null && revitZone.Phase.Name != targetPhase.Name) + { + Doc.Delete(revitZone.Id); + + revitZone = string.IsNullOrEmpty(speckleSpace.zone.name) + ? ConvertZoneToRevit(speckleSpace.zone, out _) + : CreateRevitZone(level, targetPhase); + + revitZone.Name = speckleSpace.zone.name; + } + + return revitZone; + } + + public RevitZone ZoneToSpeckle(DB.Zone revitZone) + { + var speckleZone = new RevitZone + { + name = revitZone.Name, + area = GetParamValue(revitZone, BuiltInParameter.ROOM_AREA), + volume = GetParamValue(revitZone, BuiltInParameter.ROOM_VOLUME), + perimeter = GetParamValue(revitZone, BuiltInParameter.ZONE_PERIMETER), + serviceType = revitZone.ServiceType.ToString() + }; + + var level = revitZone.Spaces.Cast().Select(x => x.Level).First(); + + if (level != null) + speckleZone.level = LevelToSpeckle(level); + + GetAllRevitParamsAndIds(speckleZone, revitZone); + + // No implicit displayValue + // speckleZone.displayValue = GetElementDisplayValue(revitSpace); + + speckleZone["phaseName"] = revitZone.Phase.Name; + + return speckleZone; + } + } +} diff --git a/Objects/Objects/BuiltElements/Space.cs b/Objects/Objects/BuiltElements/Space.cs index ea3812e369..594100d1cb 100644 --- a/Objects/Objects/BuiltElements/Space.cs +++ b/Objects/Objects/BuiltElements/Space.cs @@ -1,4 +1,6 @@ +using System; using System.Collections.Generic; +using Objects.BuiltElements.Revit; using Objects.Geometry; using Speckle.Core.Kits; using Speckle.Core.Models; @@ -53,11 +55,19 @@ double baseOffset public List voids { get; set; } = new(); public ICurve outline { get; set; } public string spaceType { get; set; } - public string zoneName { get; set; } + // add the zone object for better forward compatibility + public RevitZone? zone { get; set; } + + [Obsolete] + public string zoneName { get; internal set; } public string units { get; set; } - // additional properties to add: also inclue space separation lines here? Phase? Associated Room? Zone object instead of id? + public string roomId { get; set; } + + public string phaseName { get; set; } + + // additional properties to add: also include space separation lines here? [DetachProperty] public List displayValue { get; set; } diff --git a/Objects/Objects/BuiltElements/Zone.cs b/Objects/Objects/BuiltElements/Zone.cs new file mode 100644 index 0000000000..64d6f8b297 --- /dev/null +++ b/Objects/Objects/BuiltElements/Zone.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using Objects.Geometry; +using Speckle.Core.Kits; +using Speckle.Core.Models; + +namespace Objects.BuiltElements +{ + public class Zone : Base, IHasArea, IHasVolume + { + public Zone() { } + + public Zone(string name) + { + this.name = name; + } + + public string name { get; set; } + public string units { get; set; } + + public List spaces { get; set; } + + // implicit measurements + public double area { get; set; } + public double volume { get; set; } + public double perimeter { get; set; } + } +} + +namespace Objects.BuiltElements.Revit +{ + public class RevitZone : Zone + { + public RevitZone() { } + + public Level level { get; set; } + public string phaseName { get; set; } + public Base parameters { get; set; } + public string elementId { get; set; } + public bool isDefault { get; set; } + public string serviceType { get; set; } + } +} From b67a274cd3ec16d5f2716afbe3813e8d1f76f03a Mon Sep 17 00:00:00 2001 From: connorivy <43247197+connorivy@users.noreply.github.com> Date: Mon, 4 Sep 2023 13:51:11 -0500 Subject: [PATCH 27/57] Fix(Revit) : fix units that don't scale linearly (#2882) * change scale type depending on unit type * combine both methods * use hashset instead * just use the revit method... * get rid of unused extension --------- Co-authored-by: Connor Ivy --- .../ConverterRevitShared/ConversionUtils.cs | 2 +- .../Extensions/ForgeTypeIdExtensions.cs | 2 +- .../Partial Classes/Units.cs | 40 +++++-------------- 3 files changed, 12 insertions(+), 32 deletions(-) diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/ConversionUtils.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/ConversionUtils.cs index ffdcb53410..b2ee863f45 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/ConversionUtils.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/ConversionUtils.cs @@ -378,7 +378,7 @@ private static object GetParameterValue( unitTypeId = UnitsToNative(unitsOverride); } unitType = UnitsToNativeString(unitTypeId); - return cache != null ? ScaleToSpeckle(val, unitTypeId, cache) : ScaleToSpeckleStatic(val, unitTypeId); + return ScaleToSpeckle(val, unitTypeId); case StorageType.Integer: var intVal = rp.AsInteger(); return definition.IsBool() ? Convert.ToBoolean(intVal) : intVal; diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Extensions/ForgeTypeIdExtensions.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Extensions/ForgeTypeIdExtensions.cs index cf01b11fbe..63947da575 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/Extensions/ForgeTypeIdExtensions.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Extensions/ForgeTypeIdExtensions.cs @@ -24,7 +24,7 @@ public static class ForgeTypeIdExtensions } public static string ToUniqueString(this ForgeTypeId forgeTypeId) { - return forgeTypeId.TypeId.ToString(); + return forgeTypeId.TypeId; } } } diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/Units.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/Units.cs index 5964015ace..6d49bddb7e 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/Units.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/Units.cs @@ -1,4 +1,5 @@ using Autodesk.Revit.DB; +using ConverterRevitShared.Extensions; using RevitSharedResources.Interfaces; namespace Objects.Converter.Revit @@ -71,22 +72,22 @@ public double ScaleToNative(double value, DisplayUnitType units) /// public double ScaleToSpeckle(double value) { - return ScaleToSpeckleStatic(value, RevitLengthTypeId); + return ScaleToSpeckle(value, RevitLengthTypeId); } - public static double ScaleToSpeckleStatic(double value, DisplayUnitType unitType) + public static double ScaleToSpeckle(double value, DisplayUnitType unitType) { return UnitUtils.ConvertFromInternalUnits(value, unitType); } public static double ScaleToSpeckle(double value, DisplayUnitType unitType, IRevitDocumentAggregateCache cache) { - return ScaleToSpeckleStatic(value, unitType); + return ScaleToSpeckle(value, unitType); } public static double ScaleToSpeckle(double value, string units) { - return ScaleToSpeckleStatic(value, UnitsToNative(units)); + return ScaleToSpeckle(value, UnitsToNative(units)); } private string UnitsToSpeckle(DisplayUnitType type) @@ -178,40 +179,19 @@ public double ScaleToSpeckle(double value) return value * defaultConversionFactor.Value; } - /// - /// this method does not take advantage of any caching. Prefer other implementations of ScaleToSpeckle - /// - /// - /// - /// public static double ScaleToSpeckle(double value, string units) { - return ScaleToSpeckleStatic(value, UnitsToNative(units)); + return ScaleToSpeckle(value, UnitsToNative(units)); } - /// - /// this method does not take advantage of any caching. Prefer other implementations of ScaleToSpeckle - /// - /// - /// - /// - public static double ScaleToSpeckleStatic(double value, ForgeTypeId forgeTypeId) + public static double ScaleToSpeckle(double value, ForgeTypeId forgeTypeId) { + // our current profiling shows that the method "ConvertFromInternalUnits" is a huge bottleneck + // in the ScaleToSpeckle(double) method, but not here. This is because the former method is called + // roughly 12 times more often than this function return UnitUtils.ConvertFromInternalUnits(value, forgeTypeId); } - public static double ScaleToSpeckle(double value, ForgeTypeId forgeTypeId, IRevitDocumentAggregateCache cache) - { - return value * cache - .GetOrInitializeEmptyCacheOfType(out _) - .GetOrAdd(forgeTypeId.TypeId, () => UnitUtils.ConvertFromInternalUnits(1, forgeTypeId), out _); - } - - public double ScaleToSpeckle(double value, ForgeTypeId forgeTypeId) - { - return ScaleToSpeckle(value, forgeTypeId, revitDocumentAggregateCache); - } - //new units api introduced in 2021, bleah public string UnitsToSpeckle(string typeId) { From d33aa524725422f0cf4895c3d75f974849871c9c Mon Sep 17 00:00:00 2001 From: connorivy <43247197+connorivy@users.noreply.github.com> Date: Mon, 4 Sep 2023 14:49:29 -0500 Subject: [PATCH 28/57] Fix(Revit) : add params with null values and element ids (#2887) * add struct cache for params * clean up ParameterToSpeckle method * clean up old methods * don't set null value parameters --------- Co-authored-by: Connor Ivy --- .../ConverterRevitShared/ConversionUtils.cs | 123 ++++++------------ .../ConverterRevitShared.projitems | 2 + .../Extensions/ParameterExtensions.cs | 65 +++++++++ .../Models/ParameterToSpeckleData.cs | 34 +++++ .../Partial Classes/Units.cs | 2 +- .../Objects/BuiltElements/Revit/Parameter.cs | 3 +- 6 files changed, 144 insertions(+), 85 deletions(-) create mode 100644 Objects/Converters/ConverterRevit/ConverterRevitShared/Extensions/ParameterExtensions.cs create mode 100644 Objects/Converters/ConverterRevit/ConverterRevitShared/Models/ParameterToSpeckleData.cs diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/ConversionUtils.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/ConversionUtils.cs index b2ee863f45..09bc0d3fd0 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/ConversionUtils.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/ConversionUtils.cs @@ -6,6 +6,7 @@ using Autodesk.Revit.DB; using ConverterRevitShared.Extensions; using Objects.BuiltElements.Revit; +using Objects.Converter.Revit.Models; using Objects.Geometry; using Objects.Other; using RevitSharedResources.Interfaces; @@ -274,24 +275,13 @@ private void AddElementParamsToDict( using var parameters = element.Parameters; foreach (DB.Parameter param in parameters) { - // exclude parameters that don't have a value and those pointing to other elements as we don't support them - if (param.StorageType == StorageType.ElementId || !param.HasValue) - { - continue; - } - var internalName = GetParamInternalName(param); if (paramDict.ContainsKey(internalName) || exclusions.Contains(internalName)) { continue; } - var speckleParam = ParameterToSpeckle( - param, - isTypeParameter, - paramInternalName: internalName, - cache: revitDocumentAggregateCache - ); + var speckleParam = ParameterToSpeckle(param, internalName, isTypeParameter); paramDict[internalName] = speckleParam; } } @@ -311,7 +301,7 @@ public static T GetParamValue(DB.Element elem, BuiltInParameter bip, string u if (rp == null || !rp.HasValue) return default; - var value = GetParameterValue(rp, rp.Definition, out _, unitsOverride); + var value = rp.GetValue(rp.Definition, unitsOverride); if (typeof(T) == typeof(int) && value.GetType() == typeof(bool)) return (T)Convert.ChangeType(value, typeof(int)); @@ -325,76 +315,47 @@ public static T GetParamValue(DB.Element elem, BuiltInParameter bip, string u /// Defaults to false. True if this is a type parameter /// The units in which to return the value in the case where you want to override the Built-In 's units /// - private static Parameter ParameterToSpeckle( + private Parameter ParameterToSpeckle( DB.Parameter rp, + string paramInternalName, bool isTypeParameter = false, - string unitsOverride = null, - string paramInternalName = null, - IRevitDocumentAggregateCache cache = null + string unitsOverride = null ) { - var definition = rp.Definition; - var sp = new Parameter - { - name = definition.Name, - applicationInternalName = paramInternalName ?? GetParamInternalName(rp), - isShared = rp.IsShared, - isReadOnly = rp.IsReadOnly, - isTypeParameter = isTypeParameter, - applicationUnitType = definition.GetUnityTypeString() //eg UT_Length - }; - - sp.units = GetSymbolUnit(rp, definition, cache, out var unitTypeId); - sp.value = GetParameterValue(rp, definition, out var appUnit, unitsOverride, cache, unitTypeId); - sp.applicationUnit = appUnit; - return sp; - } - - private static object GetParameterValue( - DB.Parameter rp, - Definition definition, - out string unitType, - string unitsOverride = null, - IRevitDocumentAggregateCache cache = null, #if REVIT2020 - DisplayUnitType unitTypeId = default + DisplayUnitType unitTypeId = default; #else - ForgeTypeId unitTypeId = null + ForgeTypeId unitTypeId = null; #endif - ) - { - unitType = null; - switch (rp.StorageType) - { - case StorageType.Double: - // NOTE: do not use p.AsDouble() as direct input for unit utils conversion, it doesn't work. ¯\_(ツ)_/¯ - var val = rp.AsDouble(); - if (unitsOverride == null) + + // TODO : could add some generic getOrAdd overloads to avoid creating closures + var paramData = revitDocumentAggregateCache + .GetOrInitializeEmptyCacheOfType(out _) + .GetOrAdd(paramInternalName, () => + { + var definition = rp.Definition; + var newParamData = new ParameterToSpeckleData() { - unitTypeId = unitTypeId == default ? rp.GetUnitTypeId() : unitTypeId; - } - else + Definition = definition, + InternalName = paramInternalName, + IsReadOnly = rp.IsReadOnly, + IsShared = rp.IsShared, + IsTypeParameter = isTypeParameter, + Name = definition.Name, + UnitType = definition.GetUnityTypeString(), + }; + if (rp.StorageType == StorageType.Double) { - unitTypeId = UnitsToNative(unitsOverride); + unitTypeId = rp.GetUnitTypeId(); + newParamData.UnitsSymbol = GetSymbolUnit(rp, definition, unitTypeId); + newParamData.ApplicationUnits = unitsOverride != null + ? UnitsToNative(unitsOverride).ToUniqueString() + : unitTypeId.ToUniqueString(); } - unitType = UnitsToNativeString(unitTypeId); - return ScaleToSpeckle(val, unitTypeId); - case StorageType.Integer: - var intVal = rp.AsInteger(); - return definition.IsBool() ? Convert.ToBoolean(intVal) : intVal; - - case StorageType.String: - return rp.AsString(); - // case StorageType.ElementId: - // // NOTE: if this collects too much garbage, maybe we can ignore it - // var id = rp.AsElementId(); - // var e = Doc.GetElement(id); - // if (e != null && CanConvertToSpeckle(e)) - // sp.value = ConvertToSpeckle(e); - // break; - default: - return null; - } + return newParamData; + }, out _); + + return paramData.GetParameterObjectWithValue(rp.GetValue(paramData.Definition, unitTypeId)); } #endregion @@ -407,28 +368,24 @@ private static object GetParameterValue( /// /// /// - public static string GetSymbolUnit( + public string GetSymbolUnit( DB.Parameter parameter, DB.Definition definition, - IRevitDocumentAggregateCache cache, #if REVIT2020 - out DisplayUnitType unitTypeId + DisplayUnitType unitTypeId #else - out ForgeTypeId unitTypeId + ForgeTypeId unitTypeId #endif ) { - unitTypeId = default; if (parameter.StorageType != StorageType.Double) { return null; } - unitTypeId = parameter.GetUnitTypeId(); - var unitTypeIdCopy = unitTypeId; - return cache + return revitDocumentAggregateCache .GetOrInitializeEmptyCacheOfType(out _) - .GetOrAdd(unitTypeId.ToUniqueString(), () => unitTypeIdCopy.GetSymbol(), out _); + .GetOrAdd(unitTypeId.ToUniqueString(), () => unitTypeId.GetSymbol(), out _); } /// @@ -483,7 +440,7 @@ public void SetInstanceParameters(Element revitElement, Base speckleElement, Lis foreach (var spk in filteredSpeckleParameters) { - if (!(spk.Value is Parameter sp) || sp.isReadOnly) + if (!(spk.Value is Parameter sp) || sp.isReadOnly || sp.value == null) continue; var rp = revitParameterById.ContainsKey(spk.Key) ? revitParameterById[spk.Key] : revitParameterByName[spk.Key]; diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/ConverterRevitShared.projitems b/Objects/Converters/ConverterRevit/ConverterRevitShared/ConverterRevitShared.projitems index a4d1302898..5b004d7df2 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/ConverterRevitShared.projitems +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/ConverterRevitShared.projitems @@ -23,6 +23,8 @@ + + diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Extensions/ParameterExtensions.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Extensions/ParameterExtensions.cs new file mode 100644 index 0000000000..43dce63f1b --- /dev/null +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Extensions/ParameterExtensions.cs @@ -0,0 +1,65 @@ +#nullable enable +using System; +using Autodesk.Revit.DB; +using Objects.Converter.Revit; + +namespace ConverterRevitShared.Extensions +{ + internal static class ParameterExtensions + { + public static object? GetValue( + this Parameter parameter, + Definition definition, + string unitsOverride + ) + { +#if REVIT2020 + DisplayUnitType? unitTypeId = null; +#else + ForgeTypeId? unitTypeId = null; +#endif + if (parameter.StorageType == StorageType.Double) + { + unitTypeId = unitsOverride != null + ? ConverterRevit.UnitsToNative(unitsOverride) + : parameter.GetUnitTypeId(); + } + return GetValue(parameter, definition, unitTypeId); + } + public static object? GetValue( + this Parameter parameter, + Definition definition, +#if REVIT2020 + DisplayUnitType? unitTypeId = null +#else + ForgeTypeId? unitTypeId = null +#endif + ) + { + switch (parameter.StorageType) + { + case StorageType.Double: + var val = parameter.AsDouble(); + if (val == default(double) && parameter.HasValue == false) + { + return null; + } + return ConverterRevit.ScaleToSpeckle(val, unitTypeId ?? parameter.GetUnitTypeId()); + case StorageType.Integer: + var intVal = parameter.AsInteger(); + if (intVal == default(int) && parameter.HasValue == false) + { + return null; + } + return definition.IsBool() ? Convert.ToBoolean(intVal) : intVal; + + case StorageType.String: + return parameter.AsString(); + case StorageType.ElementId: + return parameter.AsElementId().ToString(); + default: + return null; + } + } + } +} diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Models/ParameterToSpeckleData.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Models/ParameterToSpeckleData.cs new file mode 100644 index 0000000000..fec5e6d84b --- /dev/null +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Models/ParameterToSpeckleData.cs @@ -0,0 +1,34 @@ +#nullable enable +using Objects.BuiltElements.Revit; +using DB = Autodesk.Revit.DB; + +namespace Objects.Converter.Revit.Models +{ + internal struct ParameterToSpeckleData + { + public string ApplicationUnits; + public DB.Definition Definition; + public string InternalName; + public bool IsReadOnly; + public bool IsShared; + public bool IsTypeParameter; + public string Name; + public string UnitsSymbol; + public string UnitType; + + public Parameter GetParameterObjectWithValue(object? value) + { + return new Parameter() + { + applicationInternalName = InternalName, + applicationUnit = ApplicationUnits, + isShared = IsShared, + isReadOnly = IsReadOnly, + isTypeParameter = IsTypeParameter, + name = Name, + units = UnitsSymbol, + value = value + }; + } + } +} diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/Units.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/Units.cs index 6d49bddb7e..67910b5101 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/Units.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/Units.cs @@ -116,7 +116,7 @@ private string UnitsToSpeckle(DisplayUnitType type) } - private static DisplayUnitType UnitsToNative(string units) + public static DisplayUnitType UnitsToNative(string units) { switch (units) { diff --git a/Objects/Objects/BuiltElements/Revit/Parameter.cs b/Objects/Objects/BuiltElements/Revit/Parameter.cs index 18133943c9..39bd1ed954 100644 --- a/Objects/Objects/BuiltElements/Revit/Parameter.cs +++ b/Objects/Objects/BuiltElements/Revit/Parameter.cs @@ -1,3 +1,4 @@ +#nullable enable using Speckle.Core.Kits; using Speckle.Core.Models; @@ -24,7 +25,7 @@ public Parameter( } public string name { get; set; } - public object value { get; set; } + public object? value { get; set; } public string applicationUnitType { get; set; } //eg UnitType UT_Length public string applicationUnit { get; set; } //DisplayUnitType eg DUT_MILLIMITERS public string applicationInternalName { get; set; } //BuiltInParameterName or GUID for shared parameter From 70b601081755ad6445177c7e9ef7128e2b88fa54 Mon Sep 17 00:00:00 2001 From: Matteo Cominetti Date: Mon, 4 Sep 2023 21:38:14 +0100 Subject: [PATCH 29/57] feat(revit): adds roomId and worksetId (#2888) * feat(revit): send more parameters to speckle * feat(revit): adds more parameters * feat: re-adds the worksetId, and some comments --- .../RevitSharedResources.projitems | 3 -- .../ConverterRevitShared/ConversionUtils.cs | 7 ++++- .../Models/ParameterToSpeckleData.cs | 4 +++ .../Partial Classes/ConvertFamilyInstance.cs | 28 +++++++++++-------- 4 files changed, 26 insertions(+), 16 deletions(-) diff --git a/ConnectorRevit/RevitSharedResources/RevitSharedResources.projitems b/ConnectorRevit/RevitSharedResources/RevitSharedResources.projitems index a73b4b37fa..4f392cebd7 100644 --- a/ConnectorRevit/RevitSharedResources/RevitSharedResources.projitems +++ b/ConnectorRevit/RevitSharedResources/RevitSharedResources.projitems @@ -30,7 +30,4 @@ - - - \ No newline at end of file diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/ConversionUtils.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/ConversionUtils.cs index 09bc0d3fd0..a57e0899fd 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/ConversionUtils.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/ConversionUtils.cs @@ -249,6 +249,8 @@ public void GetAllRevitParamsAndIds(Base speckleElement, DB.Element revitElement if (phaseDemolished != null) speckleElement["phaseDemolished"] = phaseDemolished.Name; + speckleElement["worksetId"] = revitElement.WorksetId.ToString(); + var category = revitElement.Category; if (category != null) { @@ -328,6 +330,9 @@ private Parameter ParameterToSpeckle( ForgeTypeId unitTypeId = null; #endif + // The parameter definitions are cached using the ParameterToSpeckleData struct + // This is done because in the case of type and instance parameter there is lots of redundant data that needs to be extracted from the Revit DB + // Caching noticeably speeds up the send process // TODO : could add some generic getOrAdd overloads to avoid creating closures var paramData = revitDocumentAggregateCache .GetOrInitializeEmptyCacheOfType(out _) @@ -614,7 +619,7 @@ private Phase GetRevitPhase(DB.Document document, string phaseName) return null; } -#endregion + #endregion #region conversion "edit existing if possible" utilities diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Models/ParameterToSpeckleData.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Models/ParameterToSpeckleData.cs index fec5e6d84b..48fd206935 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/Models/ParameterToSpeckleData.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Models/ParameterToSpeckleData.cs @@ -4,6 +4,10 @@ namespace Objects.Converter.Revit.Models { + /// + /// This struct is used when caching parameter definitions upon sending to avoid having to deep clone the parameter object + /// This is done because all the fields except the parameter value will change + /// internal struct ParameterToSpeckleData { public string ApplicationUnits; diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertFamilyInstance.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertFamilyInstance.cs index 8e4fa983b7..bf5c9114e1 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertFamilyInstance.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertFamilyInstance.cs @@ -1,24 +1,21 @@ using System; using System.Collections.Generic; -using System.Linq; using System.DoubleNumerics; - +using System.Linq; using Autodesk.Revit.DB; using Autodesk.Revit.DB.Structure; -using DB = Autodesk.Revit.DB; - -using Speckle.Core.Models; - -using Point = Objects.Geometry.Point; -using RevitInstance = Objects.Other.Revit.RevitInstance; -using RevitSymbolElementType = Objects.BuiltElements.Revit.RevitSymbolElementType; -using Vector = Objects.Geometry.Vector; using Objects.BuiltElements.Revit; +using Objects.Organization; using RevitSharedResources.Helpers; using RevitSharedResources.Helpers.Extensions; using Speckle.Core.Logging; +using Speckle.Core.Models; +using DB = Autodesk.Revit.DB; +using Point = Objects.Geometry.Point; +using RevitInstance = Objects.Other.Revit.RevitInstance; +using RevitSymbolElementType = Objects.BuiltElements.Revit.RevitSymbolElementType; using SHC = RevitSharedResources.Helpers.Categories; -using Objects.Organization; +using Vector = Objects.Geometry.Vector; namespace Objects.Converter.Revit { @@ -42,7 +39,7 @@ public Base FamilyInstanceToSpeckle(DB.FamilyInstance revitFi, out List //if they are contained in 'subelements' then they have already been accounted for from a wall //else if they are mullions then convert them as a generic family instance but add a isUGridLine prop bool? isUGridLine = null; - if (@base == null && + if (@base == null && (revitFi.Category.Id.IntegerValue == (int)BuiltInCategory.OST_CurtainWallMullions || revitFi.Category.Id.IntegerValue == (int)BuiltInCategory.OST_CurtainWallPanels)) { @@ -99,6 +96,13 @@ public Base FamilyInstanceToSpeckle(DB.FamilyInstance revitFi, out List // add additional props to base object if (isUGridLine.HasValue) @base["isUGridLine"] = isUGridLine.Value; + if (revitFi.Room != null) + @base["roomId"] = revitFi.Room.Id.ToString(); + if (revitFi.ToRoom != null) + @base["toRoomId"] = revitFi.ToRoom.Id.ToString(); + if (revitFi.FromRoom != null) + @base["fromRoomId"] = revitFi.FromRoom.Id.ToString(); + return @base; } From 7bd59882a7f30d9278e8bc451406deca0b6e8af1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B3zsef=20L=2E=20Kiss?= <50739844+jozseflkiss@users.noreply.github.com> Date: Tue, 5 Sep 2023 06:09:11 +0200 Subject: [PATCH 30/57] feat(Archicad): Revit add support for Zones - Archicad side (#2877) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Revit add support for Zones - Archicad side * build fix --------- Co-authored-by: József L. Kiss <> --- .../AddOn/Sources/AddOn/AddOnMain.cpp | 4 +- .../Sources/AddOn/Commands/CreateZone.cpp | 11 +- .../{GetRoomData.cpp => GetZoneData.cpp} | 43 +++--- .../{GetRoomData.hpp => GetZoneData.hpp} | 6 +- .../AddOn/Sources/AddOn/Utility.cpp | 49 +++++++ .../AddOn/Sources/AddOn/Utility.hpp | 4 + .../Commands/Command_CreateRoom.cs | 9 +- .../Commands/Command_GetRoomData.cs | 8 +- .../Converters/Converters/RoomConverter.cs | 128 ++++++++++++++---- .../Converters/ElementConverterManager.cs | 4 +- .../Converters/ElementTypeProvider.cs | 29 ++-- .../ConnectorArchicad/Elements/Room.cs | 43 ++++++ Objects/Objects/BuiltElements/Room.cs | 18 ++- 13 files changed, 271 insertions(+), 85 deletions(-) rename ConnectorArchicad/AddOn/Sources/AddOn/Commands/{GetRoomData.cpp => GetZoneData.cpp} (62%) rename ConnectorArchicad/AddOn/Sources/AddOn/Commands/{GetRoomData.hpp => GetZoneData.hpp} (83%) create mode 100644 ConnectorArchicad/ConnectorArchicad/Elements/Room.cs diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/AddOnMain.cpp b/ConnectorArchicad/AddOn/Sources/AddOn/AddOnMain.cpp index 76dc800b11..b7f1f7c4ee 100644 --- a/ConnectorArchicad/AddOn/Sources/AddOn/AddOnMain.cpp +++ b/ConnectorArchicad/AddOn/Sources/AddOn/AddOnMain.cpp @@ -17,12 +17,12 @@ #include "Commands/GetElementBaseData.hpp" #include "Commands/GetObjectData.hpp" #include "Commands/GetSlabData.hpp" -#include "Commands/GetRoomData.hpp" #include "Commands/GetRoofData.hpp" #include "Commands/GetShellData.hpp" #include "Commands/GetSkylightData.hpp" #include "Commands/GetProjectInfo.hpp" #include "Commands/GetSubElementInfo.hpp" +#include "Commands/GetZoneData.hpp" #include "Commands/CreateWall.hpp" #include "Commands/CreateDoor.hpp" #include "Commands/CreateWindow.hpp" @@ -198,12 +198,12 @@ static GSErrCode RegisterAddOnCommands () CHECKERROR (ACAPI_Install_AddOnCommandHandler (NewOwned ())); CHECKERROR (ACAPI_Install_AddOnCommandHandler (NewOwned ())); CHECKERROR (ACAPI_Install_AddOnCommandHandler (NewOwned ())); - CHECKERROR (ACAPI_Install_AddOnCommandHandler (NewOwned ())); CHECKERROR (ACAPI_Install_AddOnCommandHandler (NewOwned ())); CHECKERROR (ACAPI_Install_AddOnCommandHandler (NewOwned ())); CHECKERROR (ACAPI_Install_AddOnCommandHandler (NewOwned ())); CHECKERROR (ACAPI_Install_AddOnCommandHandler (NewOwned ())); CHECKERROR (ACAPI_Install_AddOnCommandHandler (NewOwned ())); + CHECKERROR (ACAPI_Install_AddOnCommandHandler (NewOwned ())); CHECKERROR (ACAPI_Install_AddOnCommandHandler (NewOwned ())); CHECKERROR (ACAPI_Install_AddOnCommandHandler (NewOwned ())); CHECKERROR (ACAPI_Install_AddOnCommandHandler (NewOwned ())); diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateZone.cpp b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateZone.cpp index 778c15ead2..edd5cfc022 100644 --- a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateZone.cpp +++ b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/CreateZone.cpp @@ -59,6 +59,9 @@ GSErrCode CreateZone::GetElementFromObjectState (const GS::ObjectState& os, ACAPI_ELEMENT_MASK_SET (mask, API_ZoneType, poly.nArcs); zoneShape.SetToMemo (memo, Objects::ElementShape::MemoMainPolygon); + + element.zone.manual = true; + ACAPI_ELEMENT_MASK_SET (mask, API_ZoneType, manual); } if (os.Contains (Room::Height)) { @@ -68,11 +71,15 @@ GSErrCode CreateZone::GetElementFromObjectState (const GS::ObjectState& os, // The name and number of the zone if (os.Contains (Room::Name)) { - os.Get (Room::Name, element.zone.roomName); + GS::UniString str; + os.Get (Room::Name, str); + GS::ucscpy (element.zone.roomName, str.ToUStr ()); ACAPI_ELEMENT_MASK_SET (mask, API_ZoneType, roomName); } if (os.Contains (Room::Number)) { - os.Get (Room::Number, element.zone.roomNoStr); + GS::UniString str; + os.Get (Room::Number, str); + GS::ucscpy (element.zone.roomNoStr, str.ToUStr ()); ACAPI_ELEMENT_MASK_SET (mask, API_ZoneType, roomNoStr); } diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetRoomData.cpp b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetZoneData.cpp similarity index 62% rename from ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetRoomData.cpp rename to ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetZoneData.cpp index 82c9ef3a19..99a6d4ea18 100644 --- a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetRoomData.cpp +++ b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetZoneData.cpp @@ -1,4 +1,4 @@ -#include "GetRoomData.hpp" +#include "GetZoneData.hpp" #include #include "ResourceIds.hpp" #include "ObjectState.hpp" @@ -14,19 +14,19 @@ namespace AddOnCommands { -GS::String GetRoomData::GetFieldName () const +GS::String GetZoneData::GetFieldName () const { return Zones; } -API_ElemTypeID GetRoomData::GetElemTypeID () const +API_ElemTypeID GetZoneData::GetElemTypeID () const { return API_ZoneID; } -GS::ErrCode GetRoomData::SerializeElementType (const API_Element& element, +GS::ErrCode GetZoneData::SerializeElementType (const API_Element& element, const API_ElementMemo& memo, GS::ObjectState& os) const { @@ -60,30 +60,39 @@ GS::ErrCode GetRoomData::SerializeElementType (const API_Element& element, // The base point of the room double level = Utility::GetStoryLevel (element.zone.head.floorInd) + element.zone.roomBaseLev; - os.Add (Room::BasePoint, Objects::Point3D (0, 0, level)); + { + Geometry::Polygon2DData polygon; + Utility::ConstructPoly2DDataFromElementMemo (memo, polygon); + + const Box2DData boundingBox = polygon.boundBox; + GS::Array sectors; + bool res = Geometry::IntersectLineWithPolygon (polygon, + boundingBox.GetMidPoint(), + boundingBox.GetWidth() > boundingBox.GetHeight() ? Vector2D (1.0, 0.0) : Vector2D (0.0, 1.0), + §ors); + + Geometry::FreePolygon2DData (&polygon); + + Objects::Point3D basePoint (0, 0, level); + if (res && sectors.GetSize() > 0) { + Sector sector = sectors[sectors.GetSize() / 2]; + basePoint = Objects::Point3D (sector.GetMidPoint ().GetX (), sector.GetMidPoint ().GetY (), level); + } + + os.Add (Room::BasePoint, Objects::Point3D (basePoint.x, basePoint.y, basePoint.z)); + } os.Add (ElementBase::Shape, Objects::ElementShape (element.zone.poly, memo, Objects::ElementShape::MemoMainPolygon, level)); - // double polyCoords [zone.poly.nCoords*3]; - // - // for (Int32 point_index = 0, coord_index = 0; point_index < zone.poly.nCoords; ++point_index, coord_index+=3) - // { - // const API_Coord coord = (*memo.coords)[point_index]; - // polyCoords[coord_index] = coord.x; - // polyCoords[coord_index+1] = coord.y; - // polyCoords[coord_index+2] = level; - // } - // Room Props os.Add (Room::Height, element.zone.roomHeight); os.Add (Room::Area, quantity.zone.area); os.Add (Room::Volume, quantity.zone.volume); - return NoError; } -GS::String GetRoomData::GetName () const +GS::String GetZoneData::GetName () const { return GetRoomDataCommandName; } diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetRoomData.hpp b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetZoneData.hpp similarity index 83% rename from ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetRoomData.hpp rename to ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetZoneData.hpp index 37b0f6b688..f630ff3cbb 100644 --- a/ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetRoomData.hpp +++ b/ConnectorArchicad/AddOn/Sources/AddOn/Commands/GetZoneData.hpp @@ -1,4 +1,4 @@ -#ifndef GET_ROOM_DATA_HPP +#ifndef GET_ROOM_DATA_HPP #define GET_ROOM_DATA_HPP #include "GetDataCommand.hpp" @@ -7,7 +7,7 @@ namespace AddOnCommands { -class GetRoomData : public GetDataCommand { +class GetZoneData : public GetDataCommand { GS::String GetFieldName () const override; API_ElemTypeID GetElemTypeID () const override; GS::ErrCode SerializeElementType (const API_Element& elem, @@ -22,4 +22,4 @@ class GetRoomData : public GetDataCommand { } -#endif \ No newline at end of file +#endif diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/Utility.cpp b/ConnectorArchicad/AddOn/Sources/AddOn/Utility.cpp index a8da5b49d8..bac3f786b4 100644 --- a/ConnectorArchicad/AddOn/Sources/AddOn/Utility.cpp +++ b/ConnectorArchicad/AddOn/Sources/AddOn/Utility.cpp @@ -5,6 +5,7 @@ #include "FieldNames.hpp" #include "TypeNameTables.hpp" #include "ResourceStrings.hpp" +#include "Polygon2DData.h" using namespace FieldNames; namespace Utility { @@ -1157,4 +1158,52 @@ GSErrCode CreateTransform (const GS::ObjectState& os, API_Tranmat& transform) return NoError; } + +GSErrCode ConstructPoly2DDataFromElementMemo (const API_ElementMemo& memo, Geometry::Polygon2DData& polygon2DData) +{ + GSErrCode err = NoError; + + Geometry::InitPolygon2DData (&polygon2DData); + + static_assert (sizeof (API_Coord) == sizeof (Coord), "sizeof (API_Coord) != sizeof (Coord)"); + static_assert (sizeof (API_PolyArc) == sizeof (PolyArcRec), "sizeof (API_PolyArc) != sizeof (PolyArcRec)"); + + polygon2DData.nVertices = BMGetHandleSize (reinterpret_cast (memo.coords)) / sizeof (Coord) - 1; + polygon2DData.vertices = reinterpret_cast (BMAllocateHandle ((polygon2DData.nVertices + 1) * sizeof (Coord), ALLOCATE_CLEAR, 0)); + if (polygon2DData.vertices != nullptr) + BNCopyMemory (*polygon2DData.vertices, *memo.coords, (polygon2DData.nVertices + 1) * sizeof (Coord)); + else + err = APIERR_MEMFULL; + + if (err == NoError && memo.parcs != nullptr) { + polygon2DData.nArcs = BMGetHandleSize (reinterpret_cast (memo.parcs)) / sizeof (PolyArcRec); + if (polygon2DData.nArcs > 0) { + polygon2DData.arcs = reinterpret_cast (BMAllocateHandle ((polygon2DData.nArcs + 1) * sizeof (PolyArcRec), ALLOCATE_CLEAR, 0)); + if (polygon2DData.arcs != nullptr) + BNCopyMemory (*polygon2DData.arcs + 1, *memo.parcs, polygon2DData.nArcs * sizeof (PolyArcRec)); + else + err = APIERR_MEMFULL; + } + } + + if (err == NoError) { + polygon2DData.nContours = BMGetHandleSize (reinterpret_cast (memo.pends)) / sizeof (Int32) - 1; + polygon2DData.contourEnds = reinterpret_cast (BMAllocateHandle ((polygon2DData.nContours + 1) * sizeof (UIndex), ALLOCATE_CLEAR, 0)); + if (polygon2DData.contourEnds != nullptr) + BNCopyMemory (*polygon2DData.contourEnds, *memo.pends, (polygon2DData.nContours + 1) * sizeof (UIndex)); + else + err = APIERR_MEMFULL; + } + + if (err == NoError) { + Geometry::GetPolygon2DDataBoundBox (polygon2DData, &polygon2DData.boundBox); + polygon2DData.status.isBoundBoxValid = true; + } else { + Geometry::FreePolygon2DData (&polygon2DData); + } + + return err; +} + + } diff --git a/ConnectorArchicad/AddOn/Sources/AddOn/Utility.hpp b/ConnectorArchicad/AddOn/Sources/AddOn/Utility.hpp index 27ff983335..e37328bc04 100644 --- a/ConnectorArchicad/AddOn/Sources/AddOn/Utility.hpp +++ b/ConnectorArchicad/AddOn/Sources/AddOn/Utility.hpp @@ -4,6 +4,7 @@ #include "APIEnvir.h" #include "ACAPinc.h" #include "ResourceIds.hpp" +#include "Polygon2DData.h" #define UNUSED(x) (void)(x) @@ -86,6 +87,9 @@ GS::UniString ComposeLogMessage (const Int32 resourceIndex, Args... args) return GS::UniString::Printf (errMsgFromatString, args...); } +// Geometry helpers +GSErrCode ConstructPoly2DDataFromElementMemo (const API_ElementMemo& memo, Geometry::Polygon2DData& polygon2DData); + } diff --git a/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_CreateRoom.cs b/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_CreateRoom.cs index 4a4c53afec..fa8b82dde6 100644 --- a/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_CreateRoom.cs +++ b/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_CreateRoom.cs @@ -2,7 +2,6 @@ using System.Threading.Tasks; using Speckle.Core.Models; using Speckle.Newtonsoft.Json; -using Objects.BuiltElements.Archicad; namespace Archicad.Communication.Commands { @@ -12,9 +11,9 @@ sealed internal class CreateRoom : ICommand> public sealed class Parameters { [JsonProperty("zones")] - private IEnumerable Datas { get; } + private IEnumerable Datas { get; } - public Parameters(IEnumerable datas) + public Parameters(IEnumerable datas) { Datas = datas; } @@ -27,9 +26,9 @@ private sealed class Result public IEnumerable ApplicationObjects { get; private set; } } - private IEnumerable Datas { get; } + private IEnumerable Datas { get; } - public CreateRoom(IEnumerable datas) + public CreateRoom(IEnumerable datas) { Datas = datas; } diff --git a/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_GetRoomData.cs b/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_GetRoomData.cs index 2dada6fc28..86ed84d4a7 100644 --- a/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_GetRoomData.cs +++ b/ConnectorArchicad/ConnectorArchicad/Communication/Commands/Command_GetRoomData.cs @@ -6,7 +6,7 @@ namespace Archicad.Communication.Commands { - sealed internal class GetRoomData : ICommand> + sealed internal class GetRoomData : ICommand> { [JsonObject(MemberSerialization.OptIn)] public sealed class Parameters @@ -24,7 +24,7 @@ public Parameters(IEnumerable applicationIds) private sealed class Result { [JsonProperty("zones")] - public IEnumerable Rooms { get; private set; } + public IEnumerable Rooms { get; private set; } } private IEnumerable ApplicationIds { get; } @@ -34,11 +34,9 @@ public GetRoomData(IEnumerable applicationIds) ApplicationIds = applicationIds; } - public async Task> Execute() + public async Task> Execute() { var result = await HttpCommandExecutor.Execute("GetRoomData", new Parameters(ApplicationIds)); - foreach (var room in result.Rooms) - room.units = Units.Meters; return result.Rooms; } diff --git a/ConnectorArchicad/ConnectorArchicad/Converters/Converters/RoomConverter.cs b/ConnectorArchicad/ConnectorArchicad/Converters/Converters/RoomConverter.cs index baa96b96a6..d460808e05 100644 --- a/ConnectorArchicad/ConnectorArchicad/Converters/Converters/RoomConverter.cs +++ b/ConnectorArchicad/ConnectorArchicad/Converters/Converters/RoomConverter.cs @@ -5,9 +5,12 @@ using System.Threading.Tasks; using Archicad.Communication; using Archicad.Model; +using DynamicData; using Objects; +using Objects.BuiltElements; using Objects.BuiltElements.Archicad; using Objects.Geometry; +using Speckle.Core.Kits; using Speckle.Core.Models; using Speckle.Core.Models.GraphTraversal; @@ -15,14 +18,19 @@ namespace Archicad.Converters { public sealed class Room : IConverter { - public Type Type => typeof(Objects.BuiltElements.Room); + public Type Type => typeof(Archicad.Room); - public async Task> ConvertToArchicad(IEnumerable elements, CancellationToken token) + public async Task> ConvertToArchicad( + IEnumerable elements, + CancellationToken token + ) { - var rooms = new List(); + var rooms = new List(); var context = Archicad.Helpers.Timer.Context.Peek; - using (context?.cumulativeTimer?.Begin(ConnectorArchicad.Properties.OperationNameTemplates.ConvertToNative, Type.Name)) + using ( + context?.cumulativeTimer?.Begin(ConnectorArchicad.Properties.OperationNameTemplates.ConvertToNative, Type.Name) + ) { foreach (var tc in elements) { @@ -30,22 +38,53 @@ public async Task> ConvertToArchicad(IEnumerable> ConvertToArchicad(IEnumerable() : result.ToList(); } - public async Task> ConvertToSpeckle(IEnumerable elements, - CancellationToken token) + public async Task> ConvertToSpeckle( + IEnumerable elements, + CancellationToken token + ) { var elementModels = elements as ElementModelData[] ?? elements.ToArray(); - IEnumerable data = - await AsyncCommandProcessor.Execute( - new Communication.Commands.GetRoomData(elementModels.Select(e => e.applicationId)), - token); + IEnumerable data = await AsyncCommandProcessor.Execute( + new Communication.Commands.GetRoomData(elementModels.Select(e => e.applicationId)), + token + ); if (data is null) { return new List(); } List rooms = new List(); - foreach (Objects.BuiltElements.Archicad.ArchicadRoom room in data) + foreach (Archicad.Room archicadRoom in data) { - room.displayValue = - Operations.ModelConverter.MeshesToSpeckle(elementModels.First(e => e.applicationId == room.applicationId) - .model); - room.outline = Utils.PolycurveToSpeckle(room.shape.contourPolyline); - if (room.shape.holePolylines?.Count > 0) - room.voids = new List(room.shape.holePolylines.Select(Utils.PolycurveToSpeckle)); - rooms.Add(room); + Objects.BuiltElements.Archicad.ArchicadRoom speckleRoom = new Objects.BuiltElements.Archicad.ArchicadRoom(); + + // convert from Archicad to Speckle data structure + // Speckle base properties + speckleRoom.id = archicadRoom.id; + speckleRoom.applicationId = archicadRoom.applicationId; + speckleRoom.displayValue = Operations.ModelConverter.MeshesToSpeckle( + elementModels.First(e => e.applicationId == archicadRoom.applicationId).model + ); + speckleRoom.units = Units.Meters; + + // Archicad properties + speckleRoom.elementType = archicadRoom.elementType; + speckleRoom.classifications = archicadRoom.classifications; + speckleRoom.level = archicadRoom.level; + speckleRoom.height = archicadRoom.height ?? .0; + speckleRoom.shape = archicadRoom.shape; + + // downdgrade + speckleRoom.name = archicadRoom.name; + speckleRoom.number = archicadRoom.number; + speckleRoom.area = archicadRoom.area ?? .0; + speckleRoom.volume = archicadRoom.volume ?? .0; + + ElementShape.Polyline polyLine = archicadRoom.shape.contourPolyline; + Polycurve polycurve = Utils.PolycurveToSpeckle(polyLine); + speckleRoom.outline = polycurve; + if (archicadRoom.shape.holePolylines?.Count > 0) + speckleRoom.voids = new List(archicadRoom.shape.holePolylines.Select(Utils.PolycurveToSpeckle)); + + // calculate base point + speckleRoom.basePoint = archicadRoom.basePoint; + + rooms.Add(speckleRoom); } return rooms; diff --git a/ConnectorArchicad/ConnectorArchicad/Converters/ElementConverterManager.cs b/ConnectorArchicad/ConnectorArchicad/Converters/ElementConverterManager.cs index 94ddd40680..555756feac 100644 --- a/ConnectorArchicad/ConnectorArchicad/Converters/ElementConverterManager.cs +++ b/ConnectorArchicad/ConnectorArchicad/Converters/ElementConverterManager.cs @@ -166,8 +166,8 @@ bool forReceive return Converters[typeof(Floor)]; if (elementType.IsSubclassOf(typeof(Roof))) return Converters[typeof(Roof)]; - if (elementType.IsSubclassOf(typeof(Objects.BuiltElements.Room))) - return Converters[typeof(Objects.BuiltElements.Room)]; + if (elementType.IsAssignableFrom(typeof(Objects.BuiltElements.Room))) + return Converters[typeof(Archicad.Room)]; return forReceive ? DefaultConverterForReceive : DefaultConverterForSend; } diff --git a/ConnectorArchicad/ConnectorArchicad/Converters/ElementTypeProvider.cs b/ConnectorArchicad/ConnectorArchicad/Converters/ElementTypeProvider.cs index 22da5abf45..91597861b5 100644 --- a/ConnectorArchicad/ConnectorArchicad/Converters/ElementTypeProvider.cs +++ b/ConnectorArchicad/ConnectorArchicad/Converters/ElementTypeProvider.cs @@ -6,7 +6,7 @@ using Door = Objects.BuiltElements.Archicad.ArchicadDoor; using Floor = Objects.BuiltElements.Archicad.ArchicadFloor; using Roof = Objects.BuiltElements.Archicad.ArchicadRoof; -using Room = Objects.BuiltElements.Archicad.ArchicadRoom; +using Room = Archicad.Room; using Shell = Objects.BuiltElements.Archicad.ArchicadShell; using Wall = Objects.BuiltElements.Archicad.ArchicadWall; using Window = Objects.BuiltElements.Archicad.ArchicadWindow; @@ -16,19 +16,20 @@ namespace Archicad { public static class ElementTypeProvider { - private static Dictionary _nameToType = new() { - { "Wall", typeof(Wall) }, - { "Slab", typeof(Floor) }, - { "Roof", typeof(Roof) }, - { "Shell", typeof(Shell) }, - { "Zone", typeof(Room) }, - { "Beam", typeof(Beam) }, - { "Column", typeof(Column) }, - { "Door", typeof(Door) }, - { "Window", typeof(Window) }, - { "Skylight", typeof(Skylight) } - - }; + private static Dictionary _nameToType = + new() + { + { "Wall", typeof(Wall) }, + { "Slab", typeof(Floor) }, + { "Roof", typeof(Roof) }, + { "Shell", typeof(Shell) }, + { "Zone", typeof(Room) }, + { "Beam", typeof(Beam) }, + { "Column", typeof(Column) }, + { "Door", typeof(Door) }, + { "Window", typeof(Window) }, + { "Skylight", typeof(Skylight) } + }; public static Type GetTypeByName(string name) { diff --git a/ConnectorArchicad/ConnectorArchicad/Elements/Room.cs b/ConnectorArchicad/ConnectorArchicad/Elements/Room.cs new file mode 100644 index 0000000000..ad2ccbaebf --- /dev/null +++ b/ConnectorArchicad/ConnectorArchicad/Elements/Room.cs @@ -0,0 +1,43 @@ +using Objects.Geometry; +using System.Collections.Generic; +using Objects.BuiltElements.Archicad; + +namespace Archicad +{ + public class Room + { + // Speckle-specific properties + // Base + public string? id { get; set; } + public string? applicationId { get; set; } + + // General + public string? name { get; set; } + public string? number { get; set; } + + public double? area { get; set; } + public double? volume { get; set; } + + // Helper + public Point? basePoint { get; set; } // Archicad geometry kernel needed for calculation + + // Archicad API properties + // Element base + public string? elementType { get; set; } + public List? classifications { get; set; } + public ArchicadLevel? level { get; set; } + + // Room + public double? height { get; set; } + + public ElementShape? shape { get; set; } + + public Room() { } + + public Room(string id, string applicationId) + { + this.id = id; + this.applicationId = applicationId; + } + } +} diff --git a/Objects/Objects/BuiltElements/Room.cs b/Objects/Objects/BuiltElements/Room.cs index 1a4948b37d..9fdedfa801 100644 --- a/Objects/Objects/BuiltElements/Room.cs +++ b/Objects/Objects/BuiltElements/Room.cs @@ -4,6 +4,7 @@ using Objects.Utils; using Speckle.Core.Kits; using Speckle.Core.Models; +using Speckle.Newtonsoft.Json; namespace Objects.BuiltElements { @@ -46,7 +47,7 @@ public Room( public string name { get; set; } public string number { get; set; } - public Level level { get; set; } + virtual public Level level { get; set; } public Point basePoint { get; set; } public List voids { get; set; } = new(); public ICurve outline { get; set; } @@ -70,12 +71,19 @@ namespace Objects.BuiltElements.Archicad public class ArchicadRoom : Room { // Element base - public string? /*APINullabe*/ elementType { get; set; } - public List? /*APINullabe*/ classifications { get; set; } + public string elementType { get; set; } + public List classifications { get; set; } - public ArchicadLevel? /*APINullabe*/ level { get; set; } + [JsonIgnore] + public ArchicadLevel archicadLevel { get; set; } - public double? /*APINullabe*/ height { get; set; } + public override Level level + { + get => archicadLevel; + set => archicadLevel = value as ArchicadLevel ?? throw new System.ArgumentException("Must be ArchicadLevel"); + } + + public double height { get; set; } public ElementShape shape { get; set; } } From 49bcaf75985d4660bf2c072941e1400402105504 Mon Sep 17 00:00:00 2001 From: Alan Rynne Date: Tue, 5 Sep 2023 09:25:55 +0200 Subject: [PATCH 31/57] feat(rvt): Adds Block to Family conversion (#2884) * fix(rvt): Make revit converter Doc non-static * feat(rvt): First run at Block->Family conversion * temp: Commit to not loose current state * fix(rvt): temp category assignment to family * feat(rvt): New mapping option for blocks to family * fix(rvt): Reformat block file * feat(rvt): Magical Transform.Decompose fix. Pending cleanup * fix(rvt): Use DirectShape in family document+ cleanup * feat(rvt): Add Block template for 2023 * feat(rvt): 2022 and 2024 block templates. Kudos to Pavol * feat(rvt): 2020 and 2021 Block template + extension correct for 23 and 24 * fix(rvt): Remove converter block suffix * feat(rvt): Block converter docs + reshuffle --- .vscode/settings.json | 4 +- .../UI/MappingBindingsRhino.cs | 3 + .../MappingTool/MappingsViewModel.cs | 1 + .../ViewModels/MappingTool/Schemas.cs | 53 +- .../DesktopUI2/Views/MappingsControl.xaml | 18 +- .../ConverterRevitShared/ConversionUtils.cs | 11 +- .../ConverterRevitShared/ConverterRevit.cs | 21 +- .../Partial Classes/ConvertBlock.cs | 539 ++++++++++++------ .../Partial Classes/ConvertFreeformElement.cs | 15 +- .../Templates/2020/Block - Imperial.rft | Bin 0 -> 356352 bytes .../Templates/2020/Block - Metric.rft | Bin 0 -> 356352 bytes .../Templates/2021/Block - Imperial.rft | Bin 0 -> 409600 bytes .../Templates/2021/Block - Metric.rft | Bin 0 -> 409600 bytes .../Templates/2022/Block - Imperial.rft | Bin 0 -> 413696 bytes .../Templates/2022/Block - Metric.rft | Bin 0 -> 413696 bytes .../Templates/2023/Block - Imperial.rft | Bin 0 -> 356352 bytes .../Templates/2023/Block - Metric.rft | Bin 0 -> 356352 bytes .../Templates/2024/Block - Imperial.rft | Bin 0 -> 446464 bytes .../Templates/2024/Block - Metric.rft | Bin 0 -> 446464 bytes .../ConverterRhinoGh.Mappings.cs | 7 + Objects/Objects/Other/MappedBlockWrapper.cs | 19 + Objects/Objects/Other/Transform.cs | 46 +- 22 files changed, 521 insertions(+), 216 deletions(-) create mode 100755 Objects/Converters/ConverterRevit/Templates/2020/Block - Imperial.rft create mode 100755 Objects/Converters/ConverterRevit/Templates/2020/Block - Metric.rft create mode 100755 Objects/Converters/ConverterRevit/Templates/2021/Block - Imperial.rft create mode 100755 Objects/Converters/ConverterRevit/Templates/2021/Block - Metric.rft create mode 100644 Objects/Converters/ConverterRevit/Templates/2022/Block - Imperial.rft create mode 100644 Objects/Converters/ConverterRevit/Templates/2022/Block - Metric.rft create mode 100644 Objects/Converters/ConverterRevit/Templates/2023/Block - Imperial.rft create mode 100644 Objects/Converters/ConverterRevit/Templates/2023/Block - Metric.rft create mode 100644 Objects/Converters/ConverterRevit/Templates/2024/Block - Imperial.rft create mode 100644 Objects/Converters/ConverterRevit/Templates/2024/Block - Metric.rft create mode 100644 Objects/Objects/Other/MappedBlockWrapper.cs diff --git a/.vscode/settings.json b/.vscode/settings.json index 444722ff71..f5bc003aa2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,10 +8,10 @@ }, "omnisharp.enableEditorConfigSupport": true, "omnisharp.enableRoslynAnalyzers": true, - "omnisharp.defaultLaunchSolution": "SDK.slnf", "[dotnet][xml]": { "editor.defaultFormatter": "ms-dotnettools.csharp", "editor.tabSize": 2 }, - "xml.format.spaceBeforeEmptyCloseTag": false + "xml.format.spaceBeforeEmptyCloseTag": false, + "dotnet.defaultSolution": "SDK.slnf" } diff --git a/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/MappingBindingsRhino.cs b/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/MappingBindingsRhino.cs index 7f032e2d07..fe2319801a 100644 --- a/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/MappingBindingsRhino.cs +++ b/ConnectorRhino/ConnectorRhino/ConnectorRhinoShared/UI/MappingBindingsRhino.cs @@ -73,7 +73,10 @@ private List GetObjectSchemas(RhinoObject obj) result.Add(existingSchema); if (obj is InstanceObject) + { + result.Add(new BlockDefinitionViewModel()); result.Add(new RevitFamilyInstanceViewModel()); + } else switch (obj.Geometry) { diff --git a/DesktopUI2/DesktopUI2/ViewModels/MappingTool/MappingsViewModel.cs b/DesktopUI2/DesktopUI2/ViewModels/MappingTool/MappingsViewModel.cs index 60fc80505c..8434ff27bd 100644 --- a/DesktopUI2/DesktopUI2/ViewModels/MappingTool/MappingsViewModel.cs +++ b/DesktopUI2/DesktopUI2/ViewModels/MappingTool/MappingsViewModel.cs @@ -315,6 +315,7 @@ private void AddRevitInfoToSchema(List schemas) //no need to do extra stuff if ( schema is DirectShapeFreeformViewModel + || schema is BlockDefinitionViewModel || schema is RevitTopographyViewModel || schema is RevitDefaultWallViewModel || schema is RevitDefaultFloorViewModel diff --git a/DesktopUI2/DesktopUI2/ViewModels/MappingTool/Schemas.cs b/DesktopUI2/DesktopUI2/ViewModels/MappingTool/Schemas.cs index 92bfcdfbb4..99e52e0b2b 100644 --- a/DesktopUI2/DesktopUI2/ViewModels/MappingTool/Schemas.cs +++ b/DesktopUI2/DesktopUI2/ViewModels/MappingTool/Schemas.cs @@ -4,6 +4,7 @@ using System.Runtime.Serialization; using Objects.BuiltElements; using Objects.BuiltElements.Revit; +using Objects.Other; using Objects.BuiltElements.Revit.RevitRoof; using ReactiveUI; using Speckle.Core.Api; @@ -378,7 +379,6 @@ public DirectShapeFreeformViewModel() .Select(x => x.ToString()) .OrderBy(x => x) .ToList(); - ; } public override string Name => "DirectShape"; @@ -437,6 +437,57 @@ public override string GetSerializedSchema() } } +public class BlockDefinitionViewModel : Schema +{ + private List _categories; + + private string _selectedCategory = RevitCategory.GenericModel.ToString(); + + public BlockDefinitionViewModel() + { + Categories = Enum.GetValues(typeof(RevitCategory)) + .Cast() + .Select(x => x.ToString()) + .OrderBy(x => x) + .ToList(); + ; + } + + public override string Name => "New Revit Family"; + + public List Categories + { + get => _categories; + set => this.RaiseAndSetIfChanged(ref _categories, value); + } + + [DataMember] + public string SelectedCategory + { + get => _selectedCategory; + set + { + this.RaiseAndSetIfChanged(ref _selectedCategory, value); + this.RaisePropertyChanged(nameof(IsValid)); + } + } + public override string Summary => $"New Revit Family - {SelectedCategory}"; + + public override bool IsValid => !string.IsNullOrEmpty(SelectedCategory); + + public override string GetSerializedSchema() + { + var res = Enum.TryParse(SelectedCategory, out RevitCategory cat); + if (!res) + cat = RevitCategory.GenericModel; + + var ds = new MappedBlockWrapper(); //don't use the constructor + ds.category = cat.ToString(); + + return Operations.Serialize(ds); + } +} + public class RevitDefaultWallViewModel : Schema { public override string Name => "Default Wall"; diff --git a/DesktopUI2/DesktopUI2/Views/MappingsControl.xaml b/DesktopUI2/DesktopUI2/Views/MappingsControl.xaml index d160f47e97..6054bc9214 100644 --- a/DesktopUI2/DesktopUI2/Views/MappingsControl.xaml +++ b/DesktopUI2/DesktopUI2/Views/MappingsControl.xaml @@ -347,17 +347,25 @@ IsChecked="{Binding Freeform}" />--> - + + + + +