Skip to content

Commit

Permalink
Revit: Adds support for wall hosted opening families (#1452)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
connorivy and connorivy authored Aug 1, 2022
1 parent 1c39d04 commit af1aab7
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ public List<ApplicationPlaceholderObject> 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}"));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -17,6 +18,8 @@ public List<ApplicationPlaceholderObject> FamilyInstanceToNative(BuiltElements.R
DB.FamilySymbol familySymbol = GetElementType<FamilySymbol>(speckleFi);
XYZ basePoint = PointToNative(speckleFi.basePoint);
DB.Level level = ConvertLevelToRevit(speckleFi.level);


DB.FamilyInstance familyInstance = null;
var isUpdate = false;
//try update existing
Expand All @@ -26,7 +29,7 @@ public List<ApplicationPlaceholderObject> FamilyInstanceToNative(BuiltElements.R
{
new ApplicationPlaceholderObject
{applicationId = speckleFi.applicationId, ApplicationGeneratedId = docObj.UniqueId, NativeObject = docObj}
}; ;
};
if (docObj != null)
{
try
Expand All @@ -49,7 +52,17 @@ public List<ApplicationPlaceholderObject> 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)
Expand All @@ -74,7 +87,92 @@ public List<ApplicationPlaceholderObject> 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<Parameter> cutVoidsParams = familySymbol.Family.GetParameters("Cut with Voids When Loaded");
IList<Parameter> 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
Expand Down Expand Up @@ -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<Base>();
Expand All @@ -240,7 +338,7 @@ public Base FamilyInstanceToSpeckle(DB.FamilyInstance revitFi)
speckleFi.elements = convertedSubElements;
}

#endregion
#endregion

// TODO:
// revitFi.GetSubelements();
Expand All @@ -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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ public DB.Level ConvertLevelToRevit(BuiltElements.Level speckleLevel)
Report.Log($"Created Level {revitLevel.Name} {revitLevel.Id}");
}


return revitLevel;

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ public List<ApplicationPlaceholderObject> WallToNative(BuiltElements.Wall speckl
Level level = null;
var structural = false;
var baseCurve = CurveToNative(speckleWall.baseLine).get_Item(0);
List<string> joinSettings = new List<string>();
if (Settings.ContainsKey("disallow-join"))
{
joinSettings = new List<string>(Regex.Split(Settings["disallow-join"], @"\,\ "));
}

if (speckleWall is RevitWall speckleRevitWall)
{
Expand All @@ -48,19 +53,15 @@ public List<ApplicationPlaceholderObject> 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<string> joinSettings = new List<string>(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)
Expand All @@ -76,6 +77,13 @@ public List<ApplicationPlaceholderObject> 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
Expand All @@ -91,6 +99,19 @@ public List<ApplicationPlaceholderObject> 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)
Expand Down

0 comments on commit af1aab7

Please sign in to comment.