/// <summary> /// Return the outline loops of all the line segments /// </summary> public JtLoops GetOutline() { JtLoops loops = new JtLoops(1); while (0 < Count) { // Outline route taken so far List <Point2dInt> route = new List <Point2dInt>(1); // Start at minimum point route.Add(Keys.Min()); // Recursively search until a closed outline is found bool closed = GetOutlineRecursion(route); loops.Add(new JtLoop(route.ToArray())); if (closed) { // Eliminate all line segments entirely enclosed. // Truncate line segments partially enclosed and remove the inner part. // A line segment might cut through the entire loop, with both endpoints outside. } } return(loops); }
/// <summary> /// Retrieve the room plan view boundary /// polygon loops and convert to 2D integer-based. /// For optimisation and consistency reasons, /// convert all coordinates to integer values in /// millimetres. Revit precision is limited to /// 1/16 of an inch, which is abaut 1.2 mm, anyway. /// </summary> static JtLoops GetRoomLoops(Room room) { SpatialElementBoundaryOptions opt = new SpatialElementBoundaryOptions(); opt.SpatialElementBoundaryLocation = SpatialElementBoundaryLocation.Center; // loops closed //SpatialElementBoundaryLocation.Finish; // loops not closed IList <IList <BoundarySegment> > loops = room. GetBoundarySegments(opt); int nLoops = loops.Count; JtLoops jtloops = new JtLoops(nLoops); foreach (IList <BoundarySegment> loop in loops) { int nSegments = loop.Count; JtLoop jtloop = new JtLoop(nSegments); XYZ p0 = null; // loop start point XYZ p; // segment start point XYZ q = null; // segment end point foreach (BoundarySegment seg in loop) { // Todo: handle non-linear curve. // Especially: if two long lines have a // short arc in between them, skip the arc // and extend both lines. p = seg.GetCurve().GetEndPoint(0); jtloop.Add(new Point2dInt(p)); Debug.Assert(null == q || q.IsAlmostEqualTo(p), "expected last endpoint to equal current start point"); q = seg.GetCurve().GetEndPoint(1); if (_debug_output) { Debug.Print("{0} --> {1}", Util.PointString(p), Util.PointString(q)); } if (null == p0) { p0 = p; // save loop start point } } Debug.Assert(q.IsAlmostEqualTo(p0), "expected last endpoint to equal loop start point"); jtloops.Add(jtloop); } return(jtloops); }
public Result Execute( ExternalCommandData commandData, ref string message, ElementSet elements) { UIApplication uiapp = commandData.Application; UIDocument uidoc = uiapp.ActiveUIDocument; Application app = uiapp.Application; Document doc = uidoc.Document; if (null == doc) { Util.ErrorMsg("Please run this command in a valid" + " Revit project document."); return(Result.Failed); } IEnumerable <ElementId> ids = Util.GetSelectedRooms(uidoc); if ((null == ids) || (0 == ids.Count())) { return(Result.Cancelled); } View view = doc.ActiveView; SpatialElementBoundaryOptions seb_opt = new SpatialElementBoundaryOptions(); Dictionary <int, JtLoops> booleanLoops = new Dictionary <int, JtLoops>( ids.Count <ElementId>()); foreach (ElementId id in ids) { Room room = doc.GetElement(id) as Room; JtLoops loops = ClipperRvt.GetRoomOuterBoundaryLoops( room, seb_opt, view); if (null == loops) // the room may not be bounded { continue; } booleanLoops.Add(id.IntegerValue, loops); } JtWindowHandle hwnd = new JtWindowHandle( uiapp.MainWindowHandle); Util.CreateOutput("room_outer_outline", "Room Outer Outline", doc, hwnd, booleanLoops); return(Result.Succeeded); }
/// <summary> /// Unite two collections of boundary /// loops into one single one. /// </summary> public static JtLoops operator +(JtLoops a, JtLoops b) { int na = a.Count; int nb = b.Count; JtLoops sum = new JtLoops(na + nb); sum.AddRange(a); sum.AddRange(b); return(sum); }
/// <summary> /// List all the loops retrieved /// from the given element. /// </summary> internal static void ListLoops(Element e, JtLoops loops) { int nLoops = loops.Count; Debug.Print("{0} has {1}{2}", Util.ElementDescription(e), Util.PluralString(nLoops, "loop"), Util.DotOrColon(nLoops)); int i = 0; foreach (JtLoop loop in loops) { Debug.Print(" {0}: {1}", i++, loop.ToString()); } }
/// <summary> /// Retrieve all plan view boundary loops from /// all solids of given element. This initial /// version passes each solid encountered in the /// given element to the ExtrusionAnalyzer one /// at a time, which obviously results in multiple /// loops, many of which are contained within the /// others. An updated version unites all the /// solids first and then uses the ExtrusionAnalyzer /// once only to obtain the true outside shadow /// contour. /// </summary> static JtLoops GetPlanViewBoundaryLoopsMultiple( Element e, ref int nFailures) { Autodesk.Revit.Creation.Application creapp = e.Document.Application.Create; JtLoops loops = new JtLoops(1); //int nSolids = 0; Options opt = new Options(); GeometryElement geo = e.get_Geometry(opt); if (null != geo) { Document doc = e.Document; if (e is FamilyInstance) { geo = geo.GetTransformed( Transform.Identity); } //GeometryInstance inst = null; foreach (GeometryObject obj in geo) { AddLoops(creapp, loops, obj, ref nFailures); //inst = obj as GeometryInstance; } //if( 0 == nSolids && null != inst ) //{ // geo = inst.GetSymbolGeometry(); // foreach( GeometryObject obj in geo ) // { // AddLoops( creapp, loops, obj, ref nFailures ); // } //} } return(loops); }
/// <summary> /// Convert Clipper polygons to JtLoops /// </summary> JtLoops ConvertToLoops(Polygons union) { JtLoops loops = new JtLoops(union.Count); JtLoop loop = new JtLoop(union.First <Polygon>().Count); foreach (Polygon poly in union) { loop.Clear(); foreach (IntPoint p in poly) { loop.Add(new Point2dInt( (int)p.X, (int)p.Y)); } loops.Add(loop); } return(loops); }
//List<Curve> GetCurves( Element e, Options opt ) //{ // GeometryElement geo = e.get_Geometry( opt ); // List<Curve> curves = new List<Curve>(); // List<Solid> solids = new List<Solid>(); // AddCurvesAndSolids( geo, curves, solids ); // return curves; //} //JtLoops GetLoops( Element e, Options opt ) //{ // List<Curve> curves = GetCurves( e, opt ); // JtLoops loops = null; // return loops; //} /// <summary> /// Return loops for outer 2D outline /// of the given element ids. /// - Retrieve geometry curves from edges /// - Convert to linear segments and 2D integer coordinates /// - Convert to non-intersecting line segments /// - Start from left-hand bottom point /// - Go down, then right /// - Keep following right-mostconection until closed loop is found /// </summary> public EdgeLoopRetriever( Options opt, ICollection <ElementId> ids) { Document doc = opt.View.Document; List <Curve> curves = new List <Curve>(); List <Solid> solids = new List <Solid>(); foreach (ElementId id in ids) { curves.Clear(); solids.Clear(); // Retrieve element geometry Element e = doc.GetElement(id); GeometryElement geo = e.get_Geometry(opt); AddCurvesAndSolids(geo, curves, solids); // Extract curves from solids AddCurvesFromSolids(curves, solids); // Flatten and simplify to line unique segments // of non-zero length with 2D integer millimetre // coordinates JtLineCollection lines = new JtLineCollection( curves); // Todo: Chop at each intersection, eliminating // all non-endpoint intersections // Contour following: // Regardless whether loop is closed or not, add it regardless. // Remove the line segments forming it and all contained within it. // If one endpoint is within and one outside, we have a relevant intersection. // Remove the line segment within, and shorten the line segment outside to the lloop edge. JtLoops loops = lines.GetOutline(); _loops.Add(id.IntegerValue, loops); } }
/// <summary> /// Add all plan view boundary loops from /// given solid to the list of loops. /// The creation application argument is used to /// reverse the extrusion analyser output curves /// in case they are badly oriented. /// </summary> /// <returns>Number of loops added</returns> static int AddLoops( Autodesk.Revit.Creation.Application creapp, JtLoops loops, GeometryObject obj, ref int nExtrusionAnalysisFailures) { int nAdded = 0; Solid solid = obj as Solid; if (null != solid && 0 < solid.Faces.Size) { //Plane plane = new Plane(XYZ.BasisX, // XYZ.BasisY, XYZ.Zero); // 2016 Plane plane = Plane.CreateByOriginAndBasis( XYZ.Zero, XYZ.BasisX, XYZ.BasisY); // 2017 ExtrusionAnalyzer extrusionAnalyzer = null; try { extrusionAnalyzer = ExtrusionAnalyzer.Create( solid, plane, XYZ.BasisZ); } catch (Autodesk.Revit.Exceptions .InvalidOperationException) { ++nExtrusionAnalysisFailures; return(nAdded); } Face face = extrusionAnalyzer .GetExtrusionBase(); loops.Add(GetLoop(creapp, face)); ++nAdded; } return(nAdded); }
/// <summary> /// Retrieve plan view boundary loops from element /// solids using ExtrusionAnalyzer. /// </summary> static Dictionary <int, JtLoops> GetSolidLoops( Document doc, ICollection <ElementId> ids) { Dictionary <int, JtLoops> solidLoops = new Dictionary <int, JtLoops>(); int nFailures; foreach (ElementId id in ids) { Element e = doc.GetElement(id); if (e is Dimension) { continue; } Debug.Print(e.Name + " " + id.IntegerValue.ToString()); nFailures = 0; JtLoops loops = CmdUploadRooms.GetSolidPlanViewBoundaryLoops( e, false, ref nFailures); if (0 < nFailures) { Debug.Print("{0}: {1}", Util.ElementDescription(e), Util.PluralString(nFailures, "extrusion analyser failure")); } CmdUploadRooms.ListLoops(e, loops); loops.NormalizeLoops(); solidLoops.Add(id.IntegerValue, loops); } return(solidLoops); }
/// <summary> /// Retrieve all plan view boundary loops from /// all solids of given element united together. /// If the element is a family instance, transform /// its loops from the instance placement /// coordinate system back to the symbol /// definition one. /// If no geometry can be determined, use the /// bounding box instead. /// </summary> internal static JtLoops GetSolidPlanViewBoundaryLoops( Element e, bool transformInstanceCoordsToSymbolCoords, ref int nFailures) { Autodesk.Revit.Creation.Application creapp = e.Document.Application.Create; JtLoops loops = null; Options opt = new Options(); GeometryElement geo = e.get_Geometry(opt); if (null != geo) { Document doc = e.Document; if (e is FamilyInstance) { if (transformInstanceCoordsToSymbolCoords) { // Retrieve family instance geometry // transformed back to symbol definition // coordinate space by inverting the // family instance placement transformation LocationPoint lp = e.Location as LocationPoint; Transform t = Transform.CreateTranslation( -lp.Point); Transform r = Transform.CreateRotationAtPoint( XYZ.BasisZ, -lp.Rotation, lp.Point); geo = geo.GetTransformed(t * r); } else { Debug.Assert( 1 == geo.Count <GeometryObject>(), "expected as single geometry instance"); Debug.Assert( geo.First <GeometryObject>() is GeometryInstance, "expected as single geometry instance"); geo = (geo.First <GeometryObject>() as GeometryInstance).GetInstanceGeometry(); } } loops = GetPlanViewBoundaryLoopsGeo( creapp, geo, ref nFailures); } if (null == loops || 0 == loops.Count) { Debug.Print( "Unable to determine geometry for " + Util.ElementDescription(e) + "; using bounding box instead."); BoundingBoxXYZ bb; if (e is FamilyInstance) { bb = (e as FamilyInstance).Symbol .get_BoundingBox(null); } else { bb = e.get_BoundingBox(null); } JtLoop loop = new JtLoop(4); loop.Add(new Point2dInt(bb.Min)); loop.Add(new Point2dInt(bb.Max.X, bb.Min.Y)); loop.Add(new Point2dInt(bb.Max)); loop.Add(new Point2dInt(bb.Min.X, bb.Max.Y)); loops.Add(loop); } return(loops); }
/// <summary> /// Retrieve all plan view boundary loops from /// all solids of the given element geometry /// united together. /// </summary> internal static JtLoops GetPlanViewBoundaryLoopsGeo( Autodesk.Revit.Creation.Application creapp, GeometryElement geo, ref int nFailures) { Solid union = null; Plane plane = Plane.CreateByOriginAndBasis( XYZ.Zero, XYZ.BasisX, XYZ.BasisY); foreach (GeometryObject obj in geo) { Solid solid = obj as Solid; if (null != solid && 0 < solid.Faces.Size) { // Some solids, e.g. in the standard // content 'Furniture Chair - Office' // cause an extrusion analyser failure, // so skip adding those. try { ExtrusionAnalyzer extrusionAnalyzer = ExtrusionAnalyzer.Create( solid, plane, XYZ.BasisZ); } catch (Autodesk.Revit.Exceptions .InvalidOperationException) { solid = null; ++nFailures; } if (null != solid) { if (null == union) { union = solid; } else { try { union = BooleanOperationsUtils .ExecuteBooleanOperation(union, solid, BooleanOperationsType.Union); } catch (Autodesk.Revit.Exceptions .InvalidOperationException) { ++nFailures; } } } } } JtLoops loops = new JtLoops(1); AddLoops(creapp, loops, union, ref nFailures); return(loops); }
public Result Execute( ExternalCommandData commandData, ref string message, ElementSet elements) { UIApplication uiapp = commandData.Application; UIDocument uidoc = uiapp.ActiveUIDocument; Application app = uiapp.Application; Document doc = uidoc.Document; if (null == doc) { Util.ErrorMsg("Please run this command in a valid" + " Revit project document."); return(Result.Failed); } ICollection <ElementId> ids = Util.GetSelectedElements(uidoc); if ((null == ids) || (0 == ids.Count)) { return(Result.Cancelled); } // Third attempt: create the element 2D outline // from element solid faces and meshes in current // view by projecting them onto the XY plane and // executing 2d Boolean unions on them. View view = doc.ActiveView; Options opt = new Options { View = view }; Clipper c = new Clipper(); VertexLookup vl = new VertexLookup(); List <LineSegment> curves = new List <LineSegment>(); Polygons union = new Polygons(); Dictionary <int, JtLoops> booleanLoops = new Dictionary <int, JtLoops>(ids.Count); foreach (ElementId id in ids) { Element e = doc.GetElement(id); GeometryElement geo = e.get_Geometry(opt); c.Clear(); vl.Clear(); union.Clear(); AddToUnion(union, curves, vl, c, geo); //AddToUnion( union, vl, c, curves ); //c.AddPaths( subjects, PolyType.ptSubject, true ); //c.AddPaths( clips, PolyType.ptClip, true ); bool succeeded = c.Execute(ClipType.ctUnion, union, PolyFillType.pftPositive, PolyFillType.pftPositive); if (0 == union.Count) { Debug.Print(string.Format( "No outline found for {0} <{1}>", e.Name, e.Id.IntegerValue)); } else { JtLoops loops = ConvertToLoops(union); loops.NormalizeLoops(); booleanLoops.Add(id.IntegerValue, loops); } } string filepath = Path.Combine(Util.OutputFolderPath, doc.Title + "_element_2d_boolean_outline.json"); JtWindowHandle hwnd = new JtWindowHandle(uiapp.MainWindowHandle); string caption = doc.Title + " 2D Booleans"; Util.ExportLoops(filepath, hwnd, caption, doc, booleanLoops); return(Result.Succeeded); }
GetElementLoops( View view, ICollection <ElementId> ids) { Document doc = view.Document; Options opt = new Options { View = view }; Clipper c = new Clipper(); VertexLookup vl = new VertexLookup(); List <LineSegment> curves = new List <LineSegment>(); Polygons union = new Polygons(); Dictionary <int, JtLoops> booleanLoops = new Dictionary <int, JtLoops>(ids.Count); foreach (ElementId id in ids) { c.Clear(); vl.Clear(); union.Clear(); Element e = doc.GetElement(id); if (e is Room) { IList <IList <BoundarySegment> > boundary = (e as Room).GetBoundarySegments( new SpatialElementBoundaryOptions()); // Ignore all loops except first, which is // hopefully outer -- and hopefully the room // does not have several disjunct parts. AddToUnionRoom(union, curves, vl, c, boundary); } else { GeometryElement geo = e.get_Geometry(opt); AddToUnion(union, curves, vl, c, geo); } //AddToUnion( union, vl, c, curves ); //c.AddPaths( subjects, PolyType.ptSubject, true ); //c.AddPaths( clips, PolyType.ptClip, true ); bool succeeded = c.Execute(ClipType.ctUnion, union, PolyFillType.pftPositive, PolyFillType.pftPositive); if (0 == union.Count) { Debug.Print(string.Format( "No outline found for {0} <{1}>", e.Name, e.Id.IntegerValue)); } else { JtLoops loops = ConvertToLoops(union); loops.NormalizeLoops(); booleanLoops.Add(id.IntegerValue, loops); } } return(booleanLoops); }
//static List<ElementId> GetRoomBoundaryIds( // Room room, // SpatialElementBoundaryOptions seb_opt ) //{ // List<ElementId> ids = null; // IList<IList<BoundarySegment>> sloops // = room.GetBoundarySegments( seb_opt ); // if( null != sloops ) // the room may not be bounded // { // Debug.Assert( 1 == sloops.Count, "this add-in " // + "currently supports only rooms with one " // + "single boundary loop" ); // ids = new List<ElementId>(); // foreach( IList<BoundarySegment> sloop in sloops ) // { // foreach( BoundarySegment s in sloop ) // { // ids.Add( s.ElementId ); // } // // Skip out after first segement loop - ignore // // rooms with holes and disjunct parts // break; // } // } // return ids; //} /// <summary> /// Create a JtLoop representing the 2D outline of /// the given room including all its bounding elements /// by creating the inner room boundary loop and /// uniting it with the bounding elements solid faces /// and meshes in the given view, projecting /// them onto the XY plane and executing 2D Boolean /// unions on them. /// </summary> public static JtLoops GetRoomOuterBoundaryLoops( Room room, SpatialElementBoundaryOptions seb_opt, View view) { Document doc = view.Document; Options opt = new Options { View = view }; Clipper c = new Clipper(); VertexLookup vl = new VertexLookup(); List <LineSegment> curves = new List <LineSegment>(); Polygons union = new Polygons(); JtLoops loops = null; IList <IList <BoundarySegment> > boundary = room.GetBoundarySegments( new SpatialElementBoundaryOptions()); if (null != boundary) // the room may not be bounded { Debug.Assert(1 == boundary.Count, "this add-in currently supports only rooms " + "with one single boundary loop"); // Ignore all loops except first, which is // hopefully outer -- and hopefully the room // does not have several disjunct parts. // Ignore holes in the room and // multiple disjunct parts. AddToUnionRoom(union, curves, vl, c, boundary); // Retrieve bounding elements List <ElementId> ids = new List <ElementId>(); foreach (IList <BoundarySegment> loop in boundary) { foreach (BoundarySegment s in loop) { ids.Add(s.ElementId); } // Skip out after first segement loop - ignore // rooms with holes and disjunct parts break; } foreach (ElementId id in ids) { // Skip invalid element ids, generated, for // instance, by a room separator line. if (!id.Equals(ElementId.InvalidElementId)) { Element e = doc.GetElement(id); GeometryElement geo = e.get_Geometry(opt); AddToUnion(union, curves, vl, c, geo); bool succeeded = c.Execute(ClipType.ctUnion, union, PolyFillType.pftPositive, PolyFillType.pftPositive); if (0 == union.Count) { Debug.Print(string.Format( "No outline found for {0} <{1}>", e.Name, e.Id.IntegerValue)); } } } loops = ConvertToLoops(union); loops.NormalizeLoops(); } return(loops); }
/// <summary> /// Upload the selected rooms and the furniture /// they contain to the cloud database. /// </summary> public static void UploadRoom( IntPtr hwnd, Document doc, Room room) { BoundingBoxXYZ bb = room.get_BoundingBox(null); if (null == bb) { Util.ErrorMsg(string.Format("Skipping room {0} " + "because it has no bounding box.", Util.ElementDescription(room))); return; } JtLoops roomLoops = GetRoomLoops(room); ListLoops(room, roomLoops); List <Element> furniture = GetFurniture(room); // Map symbol UniqueId to symbol loop Dictionary <string, JtLoop> furnitureLoops = new Dictionary <string, JtLoop>(); // List of instances referring to symbols List <JtPlacement2dInt> furnitureInstances = new List <JtPlacement2dInt>( furniture.Count); int nFailures; foreach (FamilyInstance f in furniture) { FamilySymbol s = f.Symbol; string uid = s.UniqueId; if (!furnitureLoops.ContainsKey(uid)) { nFailures = 0; JtLoops loops = GetSolidPlanViewBoundaryLoops( f, true, ref nFailures); if (0 < nFailures) { Debug.Print("{0}: {1}", Util.ElementDescription(f), Util.PluralString(nFailures, "extrusion analyser failure")); } ListLoops(f, loops); if (0 < loops.Count) { // Assume first loop is outer one furnitureLoops.Add(uid, loops[0]); } } furnitureInstances.Add( new JtPlacement2dInt(f)); } IWin32Window revit_window = new JtWindowHandle(hwnd); string caption = doc.Title + " : " + doc.GetElement(room.LevelId).Name + " : " + room.Name; Bitmap bmp = GeoSnoop.DisplayRoom(roomLoops, furnitureLoops, furnitureInstances); GeoSnoop.DisplayImageInForm(revit_window, caption, false, bmp); //DbUpload.DbUploadRoom( room, furniture, // roomLoops, furnitureLoops ); }
/// <summary> /// Display room and the furniture contained in it /// in a bitmap generated on the fly. /// </summary> /// <param name="roomLoops">Room boundary loops</param> /// <param name="geometryLoops">Family symbol geometry</param> /// <param name="familyInstances">Family instances</param> public static Bitmap DisplayRoom( JtLoops roomLoops, Dictionary <string, JtLoop> geometryLoops, List <JtPlacement2dInt> familyInstances) { JtBoundingBox2dInt bbFrom = roomLoops.BoundingBox; // Adjust target rectangle height to the // displayee loop height. int width = _form_width; int height = (int)(width * bbFrom.AspectRatio + 0.5); //SizeF fsize = new SizeF( width, height ); //SizeF scaling = new SizeF( 1, 1 ); //PointF translation = new PointF( 0, 0 ); //GetTransform( fsize, bbFrom, // ref scaling, ref translation, true ); //Matrix transform1 = new Matrix( // new Rectangle(0,0,width,height), // bbFrom.GetParallelogramPoints()); //transform1.Invert(); // the bounding box fills the rectangle // perfectly and completely, inverted and // non-uniformly distorted: //Point2dInt pmin = bbFrom.Min; //Rectangle rect = new Rectangle( // pmin.X, pmin.Y, bbFrom.Width, bbFrom.Height ); //Point[] parallelogramPoints = new Point [] { // new Point( 0, 0 ), // upper left // new Point( width, 0 ), // upper right // new Point( 0, height ) // lower left //}; // the bounding box fills the rectangle // perfectly and completely, inverted and // non-uniformly distorted: // Specify transformation target rectangle // including a margin. int bottom = height - (_margin + _margin); Point[] parallelogramPoints = new Point[] { new Point(_margin, bottom), // upper left new Point(width - _margin, bottom), // upper right new Point(_margin, _margin) // lower left }; // Transform from native loop coordinate system // to target display coordinates. Matrix transform = new Matrix( bbFrom.Rectangle, parallelogramPoints); Bitmap bmp = new Bitmap(width, height); Graphics graphics = Graphics.FromImage(bmp); graphics.Clear(System.Drawing.Color.White); DrawLoopsOnGraphics(graphics, roomLoops.GetGraphicsPathLines(), transform); if (null != familyInstances) { List <Point[]> loops = new List <Point[]>(1); loops.Add(new Point[] { }); foreach (JtPlacement2dInt i in familyInstances) { Point2dInt v = i.Translation; Matrix placement = new Matrix(); placement.Rotate(i.Rotation); placement.Translate(v.X, v.Y, MatrixOrder.Append); placement.Multiply(transform, MatrixOrder.Append); loops[0] = geometryLoops[i.SymbolId] .GetGraphicsPathLines(); DrawLoopsOnGraphics(graphics, loops, placement); } } return(bmp); }