Files
RevitSdkSamples/SDK/Samples/FreeFormElement/CS/FreeFormElementUtils.cs
T
2019-09-11 14:43:53 +02:00

354 lines
14 KiB
C#

//
// (C) Copyright 2003-2019 by Autodesk, Inc.
//
// Permission to use, copy, modify, and distribute this software in
// object code form for any purpose and without fee is hereby granted,
// provided that the above copyright notice appears in all copies and
// that both that copyright notice and the limited warranty and
// restricted rights notice below appear in all supporting
// documentation.
//
// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS.
// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC.
// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE
// UNINTERRUPTED OR ERROR FREE.
//
// Use, duplication, or disclosure by the U.S. Government is subject to
// restrictions set forth in FAR 52.227-19 (Commercial Computer
// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii)
// (Rights in Technical Data and Computer Software), as applicable.
//
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Autodesk.Revit.DB;
using RevitFreeFormElement = Autodesk.Revit.DB.FreeFormElement;
namespace Revit.SDK.Samples.FreeFormElement.CS
{
/// <summary>
/// Utilities supporting the creation of a family containing a FreeFormElement which is cut out from existing geometry
/// </summary>
class FreeFormElementUtils
{
public enum FailureCondition
{
Success,
CurvesNotContigous,
CurveLoopAboveTarget,
NoIntersection
};
/// <summary>
/// Creates a negative block family from the geometry of the target element and boundaries.
/// </summary>
/// <remarks>This is the main implementation of the sample command.</remarks>
/// <param name="targetElement">The target solid element.</param>
/// <param name="boundaries">The selected curve element boundaries.</param>
/// <param name="familyLoadOptions">The family load options when loading the new family.</param>
/// <param name="familyTemplate">The family template.</param>
public static FailureCondition CreateNegativeBlock(Element targetElement, IList<Reference> boundaries, IFamilyLoadOptions familyLoadOptions, String familyTemplate)
{
Document doc = targetElement.Document;
Autodesk.Revit.ApplicationServices.Application app = doc.Application;
// Get curve loop for boundary
IList<Curve> curves = GetContiguousCurvesFromSelectedCurveElements(doc, boundaries);
CurveLoop loop = null;
try
{
loop = CurveLoop.Create(curves);
}
catch (Autodesk.Revit.Exceptions.ArgumentException)
{
// Curves are not contiguous
return FailureCondition.CurvesNotContigous;
}
List<CurveLoop> loops = new List<CurveLoop>();
loops.Add(loop);
// Get elevation of loop
double elevation = curves[0].GetEndPoint(0).Z;
// Get height for extrusion
BoundingBoxXYZ bbox = targetElement.get_BoundingBox(null);
double height = bbox.Max.Z - elevation;
if (height <= 1e-5)
return FailureCondition.CurveLoopAboveTarget;
height += 1;
// Create family
Document familyDoc = app.NewFamilyDocument(familyTemplate);
// Create block from boundaries
Solid block = GeometryCreationUtilities.CreateExtrusionGeometry(loops, XYZ.BasisZ, height);
// Subtract target element
IList<Solid> fromElement = GetTargetSolids(targetElement);
int solidCount = fromElement.Count;
// Merge all found solids into single one
Solid toSubtract = null;
if (solidCount == 1)
{
toSubtract = fromElement[0];
}
else if (solidCount > 1)
{
toSubtract =
BooleanOperationsUtils.ExecuteBooleanOperation(fromElement[0], fromElement[1], BooleanOperationsType.Union);
}
if (solidCount > 2)
{
for (int i = 2; i < solidCount; i++)
{
toSubtract = BooleanOperationsUtils.ExecuteBooleanOperation(toSubtract, fromElement[i],
BooleanOperationsType.Union);
}
}
// Subtract merged solid from overall block
try
{
BooleanOperationsUtils.ExecuteBooleanOperationModifyingOriginalSolid(block, toSubtract,
BooleanOperationsType.Difference);
}
catch (Autodesk.Revit.Exceptions.InvalidOperationException)
{
return FailureCondition.NoIntersection;
}
// Create freeform element
using (Transaction t = new Transaction(familyDoc, "Add element"))
{
t.Start();
RevitFreeFormElement element = Autodesk.Revit.DB.FreeFormElement.Create(familyDoc, block);
t.Commit();
}
// Load family into document
Family family = familyDoc.LoadFamily(doc, familyLoadOptions);
familyDoc.Close(false);
// Get symbol as first symbol of loaded family
FilteredElementCollector collector = new FilteredElementCollector(doc);
collector.WherePasses(new FamilySymbolFilter(family.Id));
FamilySymbol fs = collector.FirstElement() as FamilySymbol;
// Place instance at location of original curves
using (Transaction t2 = new Transaction(doc, "Place instance"))
{
t2.Start();
if (!fs.IsActive)
fs.Activate();
doc.Create.NewFamilyInstance(XYZ.Zero, fs, Autodesk.Revit.DB.Structure.StructuralType.NonStructural);
t2.Commit();
}
return FailureCondition.Success;
}
/// <summary>
/// Gets a list of curves which are ordered correctly and oriented correctly to form a closed loop.
/// </summary>
/// <param name="doc">The document.</param>
/// <param name="boundaries">The list of curve element references which are the boundaries.</param>
/// <returns>The list of curves.</returns>
public static IList<Curve> GetContiguousCurvesFromSelectedCurveElements(Document doc, IList<Reference> boundaries)
{
List<Curve> curves = new List<Curve>();
// Build a list of curves from the curve elements
foreach (Reference reference in boundaries)
{
CurveElement curveElement = doc.GetElement(reference) as CurveElement;
curves.Add(curveElement.GeometryCurve.Clone());
}
// Walk through each curve (after the first) to match up the curves in order
for (int i = 0; i < curves.Count; i++)
{
Curve curve = curves[i];
XYZ endPoint = curve.GetEndPoint(1);
// find curve with start point = end point
for (int j = i + 1; j < curves.Count; j++)
{
// Is there a match end->start, if so this is the next curve
if (curves[j].GetEndPoint(0).IsAlmostEqualTo(endPoint, 1e-05))
{
Curve tmpCurve = curves[i + 1];
curves[i + 1] = curves[j];
curves[j] = tmpCurve;
continue;
}
// Is there a match end->end, if so, reverse the next curve
else if (curves[j].GetEndPoint(1).IsAlmostEqualTo(endPoint, 1e-05))
{
Curve tmpCurve = curves[i + 1];
curves[i + 1] = CreateReversedCurve(curves[j]);
curves[j] = tmpCurve;
continue;
}
}
}
return curves;
}
/// <summary>
/// Utility to create a new curve with the same geometry but in the reverse direction.
/// </summary>
/// <param name="orig">The original curve.</param>
/// <returns>The reversed curve.</returns>
/// <throws cref="NotImplementedException">If the curve type is not supported by this utility.</throws>
private static Curve CreateReversedCurve(Curve orig)
{
if (!SupportsLoopUtilities(orig))
{
throw new NotImplementedException("CreateReversedCurve for type " + orig.GetType().Name);
}
if (orig is Line)
{
return Line.CreateBound(orig.GetEndPoint(1), orig.GetEndPoint(0));
}
else if (orig is Arc)
{
return Arc.Create(orig.GetEndPoint(1), orig.GetEndPoint(0), orig.Evaluate(0.5, true));
}
else
{
throw new Exception("CreateReversedCurve - Unreachable");
}
}
/// <summary>
/// Identifies if the curve type is supported in these utilities.
/// </summary>
/// <param name="curve">The curve.</param>
/// <returns>True if the curve type is supported, false otherwise.</returns>
public static bool SupportsLoopUtilities(Curve curve)
{
return curve is Line || curve is Arc;
}
/// <summary>
/// Identifies if the curve lies entirely in an XY plane (Z = constant)
/// </summary>
/// <param name="curve">The curve.</param>
/// <returns>True if the curve lies in an XY plane, false otherwise.</returns>
public static bool IsCurveInXYPlane(Curve curve)
{
// quick reject - are endpoints at same Z
double zDelta = curve.GetEndPoint(1).Z - curve.GetEndPoint(0).Z;
if (Math.Abs(zDelta) > 1e-05)
return false;
if (!(curve is Line) && !curve.IsCyclic)
{
// Create curve loop from curve and connecting line to get plane
List<Curve> curves = new List<Curve>();
curves.Add(curve);
curves.Add(Line.CreateBound(curve.GetEndPoint(1), curve.GetEndPoint(0)));
CurveLoop curveLoop = CurveLoop.Create(curves);
XYZ normal = curveLoop.GetPlane().Normal.Normalize();
if (!normal.IsAlmostEqualTo(XYZ.BasisZ) && !normal.IsAlmostEqualTo(XYZ.BasisZ.Negate()))
return false;
}
return true;
}
/// <summary>
/// Gets all target solids in a given element.
/// </summary>
/// <remarks>Includes solids and solids in first level instances only. Deeper levels are ignored. Empty solids are not returned.</remarks>
/// <param name="element">The element.</param>
/// <returns>The list of solids.</returns>
public static IList<Solid> GetTargetSolids(Element element)
{
List<Solid> solids = new List<Solid>();
Options options = new Options();
options.DetailLevel = ViewDetailLevel.Fine;
GeometryElement geomElem = element.get_Geometry(options);
foreach (GeometryObject geomObj in geomElem)
{
if (geomObj is Solid)
{
Solid solid = (Solid)geomObj;
if (solid.Faces.Size > 0 && solid.Volume > 0.0)
{
solids.Add(solid);
}
// Single-level recursive check of instances. If viable solids are more than
// one level deep, this example ignores them.
}
else if (geomObj is GeometryInstance)
{
GeometryInstance geomInst = (GeometryInstance)geomObj;
GeometryElement instGeomElem = geomInst.GetInstanceGeometry();
foreach (GeometryObject instGeomObj in instGeomElem)
{
if (instGeomObj is Solid)
{
Solid solid = (Solid)instGeomObj;
if (solid.Faces.Size > 0 && solid.Volume > 0.0)
{
solids.Add(solid);
}
}
}
}
}
return solids;
}
/// <summary>
/// Finds the Generic Model template from the family template directory path, if it exists.
/// </summary>
/// <param name="familyPath">The family template directory path.</param>
/// <returns>The template path, or empty string if not found.</returns>
public static String FindGenericModelTemplate(String familyPath)
{
string hardCodedResult = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(typeof(CreateNegativeBlockCommand).Assembly.Location), "Generic Model.rft");
try
{
IEnumerable<string> files = Directory.EnumerateFiles(familyPath, "Generic Model.rft", SearchOption.AllDirectories);
string result = files.FirstOrDefault<string>();
if (!String.IsNullOrEmpty(result))
return result;
files = Directory.EnumerateFiles(familyPath, "Metric Generic Model.rft", SearchOption.AllDirectories);
result = files.FirstOrDefault<string>();
if (!String.IsNullOrEmpty(result))
return result;
return hardCodedResult;
}
catch (Exception)
{
return hardCodedResult;
}
}
}
}