From af1aab79d3931bc9abe067e2b3495153ee82736d Mon Sep 17 00:00:00 2001 From: connorivy <43247197+connorivy@users.noreply.github.com> Date: Mon, 1 Aug 2022 10:56:37 -0400 Subject: [PATCH] Revit: Adds support for wall hosted opening families (#1452) * added comments in order to trace issue * more debug lines * added getting the level for hosted element * the newFamilyInstance method works, but I can't get the wall reference * Importing the walls with generic family instances work but maybe only for this specific case * need to capture orientation of hosted family * deleted throw message so loop would continue * only hosted elements allowed on faces of walls * updated void cutting and level setting to work for all versions of rev * disallow join of walls before moving them * fixed stupid location changing issue with family instance objects * return error for work plane based objs on a floor * add last resort family instance assignment Co-authored-by: connorivy --- .../ConverterRevitShared/ConversionUtils.cs | 2 +- .../Partial Classes/ConvertFamilyInstance.cs | 129 +++++++++++++++++- .../Partial Classes/ConvertLevel.cs | 1 - .../Partial Classes/ConvertWall.cs | 45 ++++-- 4 files changed, 158 insertions(+), 19 deletions(-) diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/ConversionUtils.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/ConversionUtils.cs index e6605ba6e1..c014b95e2b 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/ConversionUtils.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/ConversionUtils.cs @@ -127,7 +127,7 @@ public List SetHostedElements(Base @base, HostObje } catch (Exception e) { - throw (new Exception($"Failed to create hosted element {obj.speckle_type} in host ({host.Id}): \n{e.Message}")); + Report.ConversionErrors.Add(new Exception($"Failed to create hosted element {obj.speckle_type} in host ({host.Id}): \n{e.Message}")); } } diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertFamilyInstance.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertFamilyInstance.cs index d87bb85c17..a8a76d21a0 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertFamilyInstance.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertFamilyInstance.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Numerics; using DB = Autodesk.Revit.DB; using Point = Objects.Geometry.Point; @@ -17,6 +18,8 @@ public List FamilyInstanceToNative(BuiltElements.R DB.FamilySymbol familySymbol = GetElementType(speckleFi); XYZ basePoint = PointToNative(speckleFi.basePoint); DB.Level level = ConvertLevelToRevit(speckleFi.level); + + DB.FamilyInstance familyInstance = null; var isUpdate = false; //try update existing @@ -26,7 +29,7 @@ public List FamilyInstanceToNative(BuiltElements.R { new ApplicationPlaceholderObject {applicationId = speckleFi.applicationId, ApplicationGeneratedId = docObj.UniqueId, NativeObject = docObj} - }; ; + }; if (docObj != null) { try @@ -49,7 +52,17 @@ public List FamilyInstanceToNative(BuiltElements.R //to avoid this behavior we're always setting the previous location Z coordinate when updating an element //this means the Z coord of an element will only be set by its Level //and by additional parameters as sill height, base offset etc - (familyInstance.Location as LocationPoint).Point = new XYZ(basePoint.X, basePoint.Y, (familyInstance.Location as LocationPoint).Point.Z); + var newLocationPoint = new XYZ(basePoint.X, basePoint.Y, (familyInstance.Location as LocationPoint).Point.Z); + + (familyInstance.Location as LocationPoint).Point = newLocationPoint; + + // BAND AID FIX ALERT + // this is one of the stranger issues I've encountered. When I set the location of a family instance + // it mostly works fine, but every so often it goes to a different location than the one I set + // it seems like just reassigning the location to the same thing we just assigned it to works + // I don't know why this is happening + if ((familyInstance.Location as LocationPoint).Point != newLocationPoint) + (familyInstance.Location as LocationPoint).Point = newLocationPoint; // check for a type change if (speckleFi.type != null && speckleFi.type != revitType.Name) @@ -74,7 +87,92 @@ public List FamilyInstanceToNative(BuiltElements.R //If the current host element is not null, it means we're coming from inside a nested conversion. if (CurrentHostElement != null) { - familyInstance = Doc.Create.NewFamilyInstance(basePoint, familySymbol, CurrentHostElement, level, StructuralType.NonStructural); + if (level == null) + level = Doc.GetElement(CurrentHostElement.LevelId) as Level; + + // there are two (i think) main types of hosted elements which can be found with family.familyplacementtype + // the two placement types for hosted elements are onelevelbasedhosted and workplanebased + + if (familySymbol.Family.FamilyPlacementType == FamilyPlacementType.OneLevelBasedHosted) + { + familyInstance = Doc.Create.NewFamilyInstance(basePoint, familySymbol, CurrentHostElement, level, StructuralType.NonStructural); + } + else if (familySymbol.Family.FamilyPlacementType == FamilyPlacementType.WorkPlaneBased) + { + if (CurrentHostElement == null) + Report.ConversionErrors.Add(new Exception($"Object with ID {speckleFi.id} is work plane based, but does not have a host element")); + if (CurrentHostElement is Wall wall) + { + Doc.Regenerate(); + + Options op = new Options(); + op.ComputeReferences = true; + GeometryElement wallGeom = wall.get_Geometry(op); + Reference faceRef = null; + + foreach (var geom in wallGeom) + { + if (geom is Solid solid) + { + FaceArray faceArray = solid.Faces; + + double planeDist = double.PositiveInfinity; + foreach (Face face in faceArray) + { + if (face is PlanarFace planarFace) + { + // some family instance base points may lie on the intersection of faces + // this makes it so family instance families can only be placed on the + // faces of walls + if (NormalsAlign(planarFace.FaceNormal, wall.Orientation)) + { + double newPlaneDist = ComputePlaneDistance(planarFace.Origin, planarFace.FaceNormal, basePoint); + if (newPlaneDist < planeDist) + { + planeDist = newPlaneDist; + faceRef = planarFace.Reference; + } + } + } + } + } + } + + // last resort, just guess a face + if (faceRef == null) + faceRef = HostObjectUtils.GetSideFaces(wall, ShellLayerType.Interior)[0]; + + XYZ norm = new XYZ(0, 0, 0); + familyInstance = Doc.Create.NewFamilyInstance(faceRef, basePoint, norm, familySymbol); + + // parameters + IList cutVoidsParams = familySymbol.Family.GetParameters("Cut with Voids When Loaded"); + IList lvlParams = familyInstance.GetParameters("Schedule Level"); + + if (cutVoidsParams.ElementAtOrDefault(0) != null && cutVoidsParams[0].AsInteger() == 1) + InstanceVoidCutUtils.AddInstanceVoidCut(Doc, wall, familyInstance); + if (lvlParams.ElementAtOrDefault(0) != null) + lvlParams[0].Set(level.Id); + } + else if (CurrentHostElement is Floor floor) + { + // TODO: support hosted elements on floors. Should be very similar to above implementation + Report.ConversionErrors.Add(new Exception($"Work Plane based families on floors to be supported soon")); + } + } + else + { + Report.ConversionErrors.Add(new Exception($"Unsupported FamilyPlacementType {familySymbol.Family.FamilyPlacementType}")); + } + // try a catch all solution as a last resort + if (familyInstance == null) + { + try + { + familyInstance = Doc.Create.NewFamilyInstance(basePoint, familySymbol, CurrentHostElement, level, StructuralType.NonStructural); + } + catch { } + } } //Otherwise, proceed as normal. else @@ -215,7 +313,7 @@ public Base FamilyInstanceToSpeckle(DB.FamilyInstance revitFi) GetAllRevitParamsAndIds(speckleFi, revitFi); - #region sub elements capture +#region sub elements capture var subElementIds = revitFi.GetSubComponentIds(); var convertedSubElements = new List(); @@ -240,7 +338,7 @@ public Base FamilyInstanceToSpeckle(DB.FamilyInstance revitFi) speckleFi.elements = convertedSubElements; } - #endregion +#endregion // TODO: // revitFi.GetSubelements(); @@ -267,5 +365,26 @@ public Base FamilyInstanceToSpeckle(DB.FamilyInstance revitFi) } return subElements; } + + private double ComputePlaneDistance(XYZ planeOrigin, XYZ planeNormal, XYZ point) + { + // D = nx*ox + ny+oy nz+oz + // where planeNormal = {nx,ny,nz} and planeOrigin = {ox,oy,oz} + double D = planeNormal.X * planeOrigin.X + planeNormal.Y * planeOrigin.Y + planeNormal.Z * planeOrigin.Z; + double PointD = planeNormal.X * point.X + planeNormal.Y * point.Y + planeNormal.Z * point.Z; + double value = Math.Abs(D - PointD); + + //Report.Log($"point {point}"); + return value; + } + + private bool NormalsAlign(XYZ normal1, XYZ normal2) + { + var isXNormAligned = Math.Abs(Math.Abs(normal1.X) - Math.Abs(normal2.X)) < TOLERANCE; + var isYNormAligned = Math.Abs(Math.Abs(normal1.Y) - Math.Abs(normal2.Y)) < TOLERANCE; + var isZNormAligned = Math.Abs(Math.Abs(normal1.Z) - Math.Abs(normal2.Z)) < TOLERANCE; + + return isXNormAligned && isYNormAligned && isZNormAligned; + } } } diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertLevel.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertLevel.cs index 8392ba3455..29225b0891 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertLevel.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertLevel.cs @@ -93,7 +93,6 @@ public DB.Level ConvertLevelToRevit(BuiltElements.Level speckleLevel) Report.Log($"Created Level {revitLevel.Name} {revitLevel.Id}"); } - return revitLevel; } diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertWall.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertWall.cs index ff9e0319d5..3e70c0208a 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertWall.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertWall.cs @@ -31,6 +31,11 @@ public List WallToNative(BuiltElements.Wall speckl Level level = null; var structural = false; var baseCurve = CurveToNative(speckleWall.baseLine).get_Item(0); + List joinSettings = new List(); + if (Settings.ContainsKey("disallow-join")) + { + joinSettings = new List(Regex.Split(Settings["disallow-join"], @"\,\ ")); + } if (speckleWall is RevitWall speckleRevitWall) { @@ -48,19 +53,15 @@ public List WallToNative(BuiltElements.Wall speckl { isUpdate = false; revitWall = DB.Wall.Create(Doc, baseCurve, level.Id, structural); - if (Settings.ContainsKey("disallow-join")) + if (joinSettings.Contains(StructuralWalls) && structural) { - List joinSettings = new List(Regex.Split(Settings["disallow-join"], @"\,\ ")); - if (joinSettings.Contains(StructuralWalls) && structural) - { - WallUtils.DisallowWallJoinAtEnd(revitWall, 0); - WallUtils.DisallowWallJoinAtEnd(revitWall, 1); - } - if (joinSettings.Contains(ArchitecturalWalls) && !structural) - { - WallUtils.DisallowWallJoinAtEnd(revitWall, 0); - WallUtils.DisallowWallJoinAtEnd(revitWall, 1); - } + WallUtils.DisallowWallJoinAtEnd(revitWall, 0); + WallUtils.DisallowWallJoinAtEnd(revitWall, 1); + } + if (joinSettings.Contains(ArchitecturalWalls) && !structural) + { + WallUtils.DisallowWallJoinAtEnd(revitWall, 0); + WallUtils.DisallowWallJoinAtEnd(revitWall, 1); } } if (revitWall == null) @@ -76,6 +77,13 @@ public List WallToNative(BuiltElements.Wall speckl if (isUpdate) { + // walls behave very strangly while joined + // if a wall is joined and you try to move it to a location where it isn't touching the joined wall, + // then it will move only one end of the wall and the other will stay joined + // therefore, disallow joins while changing the wall and then reallow the joins if need be + WallUtils.DisallowWallJoinAtEnd(revitWall, 0); + WallUtils.DisallowWallJoinAtEnd(revitWall, 1); + //NOTE: updating an element location can be buggy if the baseline and level elevation don't match //Let's say the first time an element is created its base point/curve is @ 10m and the Level is @ 0m //the element will be created @ 0m @@ -91,6 +99,19 @@ public List WallToNative(BuiltElements.Wall speckl ((LocationCurve)revitWall.Location).Curve = newCurve; TrySetParam(revitWall, BuiltInParameter.WALL_BASE_CONSTRAINT, level); + + + // now that we've moved the wall, rejoin the wall ends + if (!joinSettings.Contains(StructuralWalls) && structural) + { + WallUtils.AllowWallJoinAtEnd(revitWall, 0); + WallUtils.AllowWallJoinAtEnd(revitWall, 1); + } + if (!joinSettings.Contains(ArchitecturalWalls) && !structural) + { + WallUtils.AllowWallJoinAtEnd(revitWall, 0); + WallUtils.AllowWallJoinAtEnd(revitWall, 1); + } } if (speckleWall is RevitWall spklRevitWall)