Skip to content

Commit 058696f

Browse files
BurrrNingnyqRevit
andauthored
Add Sketch node (#3395)
Wraps Autodesk.Revit.DB.Sketch as a read-only Dynamo node so graphs can inspect the sketch backing a sketch-based element (for example the Sketch returned by PropertyLine.Sketch on the new PropertyLine nodes). * New Revit.Elements.Sketch wrapper exposes the Revit 2027 public_api surface from Sketch.ridl: SketchPlane (property sketchPlane), Owner (property OwnerId, resolved to the wrapped Element), and Elements (func getAllElements). The Profile getter (PolyCurve[] per closed loop) wraps the handwritten Profile property declared in APISketchHandwritten.h. No factories are exposed -- Sketches are obtainable only via host elements -- and no mutators, since none of them are public_api. * PropertyLine.Sketch now returns the new Sketch wrapper instead of the generic Element, so PropertyLine -> Sketch -> SketchPlane / Owner / Elements / Profile chains compile without manual casts. * Adds the new node to LayoutSpecs.json so it appears under Revit > Elements > Sketch in the Dynamo library tree (placed alphabetically between Room and SketchPlane). * Adds RevitNodesTests.Elements.SketchTests covering the SketchPlane, Owner, Elements, and Profile getters by driving them through a PropertyLine-backed sketch. Please Note: 1. Before submitting the PR, please review [How to Contribute to Dynamo](https://github.com/DynamoDS/Dynamo/blob/master/CONTRIBUTING.md) 2. Dynamo Team will meet 1x a month to review PRs found on Github (Issues will be handled separately) 3. PRs will be reviewed from oldest to newest 4. If a reviewed PR requires changes by the owner, the owner of the PR has 30 days to respond. If the PR has seen no activity by the next session, it will be either closed by the team or depending on its utility will be taken over by someone on the team 5. PRs should use either Dynamo's default PR template or [one of these other template options](https://github.com/DynamoDS/Dynamo/wiki/Choosing-a-Pull-Request-Template) in order to be considered for review. 6. PRs that do not have one of the Dynamo PR templates completely filled out with all declarations satisfied will not be reviewed by the Dynamo team. 7. PRs made to the `DynamoRevit` repo will need to be cherry-picked into all the DynamoRevit Release branches that Dynamo supports. Contributors will be responsible for cherry-picking their reviewed commits to the other branches after a `LGTM` label is added to the PR. ### Purpose (FILL ME IN) This section describes why this PR is here. Usually it would include a reference to the tracking task that it is part or all of the solution for. ### Declarations Check these if you believe they are true - [ ] The code base is in a better state after this PR - [ ] Is documented according to the [standards](https://github.com/DynamoDS/Dynamo/wiki/Coding-Standards) - [ ] The level of testing this PR includes is appropriate - [ ] User facing strings, if any, are extracted into `*.resx` files - [ ] Snapshot of UI changes, if any. ### Reviewers (FILL ME IN) Reviewer 1 (If possible, assign the Reviewer for the PR) (FILL ME IN, optional) Any additional notes to reviewers or testers. ### FYIs (FILL ME IN, Optional) Names of anyone else you wish to be notified of Co-authored-by: Yueqiang Ni <yueqiang.ni@autodesk.com>
1 parent 31308e5 commit 058696f

4 files changed

Lines changed: 298 additions & 3 deletions

File tree

src/DynamoRevit/Resources/LayoutSpecs.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,9 @@
241241
{
242242
"path": "RevitNodes.Revit.Elements.Room"
243243
},
244+
{
245+
"path": "RevitNodes.Revit.Elements.Sketch"
246+
},
244247
{
245248
"path": "RevitNodes.Revit.Elements.SketchPlane"
246249
},

src/Libraries/RevitNodes/Elements/PropertyLine.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ public PropertyTableEntry[] PropertyTable
164164
/// <summary>
165165
/// The Sketch element backing a sketch-based PropertyLine, or null for a table-based one.
166166
/// </summary>
167-
public Element Sketch
167+
public Sketch Sketch
168168
{
169169
get
170170
{
@@ -174,8 +174,8 @@ public Element Sketch
174174
return null;
175175
}
176176

177-
Autodesk.Revit.DB.Element revitElement = Document.GetElement(sketchId);
178-
return revitElement?.ToDSType(true) as Element;
177+
var revitSketch = Document.GetElement(sketchId) as Autodesk.Revit.DB.Sketch;
178+
return revitSketch == null ? null : Sketch.FromExisting(revitSketch, true);
179179
}
180180
}
181181

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using Autodesk.DesignScript.Geometry;
4+
using Autodesk.DesignScript.Runtime;
5+
using Autodesk.Revit.DB;
6+
using Revit.GeometryConversion;
7+
using Curve = Autodesk.DesignScript.Geometry.Curve;
8+
using DBCurve = Autodesk.Revit.DB.Curve;
9+
using DBSketch = Autodesk.Revit.DB.Sketch;
10+
11+
namespace Revit.Elements
12+
{
13+
/// <summary>
14+
/// A Revit Sketch element. Sketches hold the model curves, reference planes
15+
/// and dimensions that define the 2-dimensional profile of a sketch-based
16+
/// element such as a Floor, Roof, or PropertyLine.
17+
/// </summary>
18+
public class Sketch : Element
19+
{
20+
#region Internal properties
21+
22+
/// <summary>
23+
/// Internal handle on the Revit Sketch.
24+
/// </summary>
25+
internal DBSketch InternalSketch { get; private set; }
26+
27+
/// <summary>
28+
/// Reference to the wrapped element.
29+
/// </summary>
30+
[SupressImportIntoVM]
31+
public override Autodesk.Revit.DB.Element InternalElement
32+
{
33+
get { return InternalSketch; }
34+
}
35+
36+
#endregion
37+
38+
#region Private constructors
39+
40+
private Sketch(DBSketch sketch)
41+
{
42+
SafeInit(() => InitSketch(sketch), true);
43+
}
44+
45+
#endregion
46+
47+
#region Helpers for private constructors
48+
49+
private void InitSketch(DBSketch sketch)
50+
{
51+
InternalSetSketch(sketch);
52+
}
53+
54+
#endregion
55+
56+
#region Private mutators
57+
58+
private void InternalSetSketch(DBSketch sketch)
59+
{
60+
InternalSketch = sketch;
61+
InternalElementId = sketch.Id;
62+
InternalUniqueId = sketch.UniqueId;
63+
}
64+
65+
#endregion
66+
67+
#region Public properties
68+
69+
/// <summary>
70+
/// The SketchPlane that this Sketch lies on.
71+
/// </summary>
72+
public SketchPlane SketchPlane
73+
{
74+
get
75+
{
76+
Autodesk.Revit.DB.SketchPlane revitSketchPlane = InternalSketch.SketchPlane;
77+
return revitSketchPlane == null
78+
? null
79+
: Revit.Elements.SketchPlane.FromExisting(revitSketchPlane, true);
80+
}
81+
}
82+
83+
/// <summary>
84+
/// The element that owns this Sketch (for example the PropertyLine, Floor,
85+
/// or Roof hosting it). Returns null when the Sketch is not yet attached
86+
/// to an owning element.
87+
/// </summary>
88+
public Element Owner
89+
{
90+
get
91+
{
92+
ElementId ownerId = InternalSketch.OwnerId;
93+
if (ownerId == null || ownerId == ElementId.InvalidElementId)
94+
{
95+
return null;
96+
}
97+
98+
Autodesk.Revit.DB.Element revitElement = Document.GetElement(ownerId);
99+
return revitElement?.ToDSType(true) as Element;
100+
}
101+
}
102+
103+
/// <summary>
104+
/// All elements that belong to this Sketch (typically model curves,
105+
/// reference planes, and dimensions).
106+
/// </summary>
107+
public Element[] Elements
108+
{
109+
get
110+
{
111+
IList<ElementId> elementIds = InternalSketch.GetAllElements();
112+
if (elementIds == null || elementIds.Count == 0)
113+
{
114+
return new Element[0];
115+
}
116+
117+
return elementIds
118+
.Select(id => Document.GetElement(id)?.ToDSType(true) as Element)
119+
.Where(e => e != null)
120+
.ToArray();
121+
}
122+
}
123+
124+
/// <summary>
125+
/// The Sketch profile as one PolyCurve per closed loop.
126+
/// </summary>
127+
public PolyCurve[] Profile
128+
{
129+
get
130+
{
131+
var loops = new List<PolyCurve>();
132+
CurveArrArray curveArrArray = InternalSketch.Profile;
133+
if (curveArrArray == null)
134+
{
135+
return loops.ToArray();
136+
}
137+
138+
foreach (CurveArray curveArray in curveArrArray)
139+
{
140+
if (curveArray == null || curveArray.Size == 0)
141+
{
142+
continue;
143+
}
144+
145+
var protoCurves = new List<Curve>(curveArray.Size);
146+
foreach (DBCurve revitCurve in curveArray)
147+
{
148+
Curve protoCurve = revitCurve?.ToProtoType(true);
149+
if (protoCurve != null)
150+
{
151+
protoCurves.Add(protoCurve);
152+
}
153+
}
154+
155+
if (protoCurves.Count == 0)
156+
{
157+
continue;
158+
}
159+
160+
try
161+
{
162+
loops.Add(PolyCurve.ByJoinedCurves(protoCurves.ToArray(), 0.001, false));
163+
}
164+
finally
165+
{
166+
foreach (Curve c in protoCurves)
167+
{
168+
c.Dispose();
169+
}
170+
}
171+
}
172+
173+
return loops.ToArray();
174+
}
175+
}
176+
177+
#endregion
178+
179+
#region Internal static constructors
180+
181+
/// <summary>
182+
/// Wraps a Revit-owned Sketch in the Dynamo Sketch wrapper.
183+
/// </summary>
184+
internal static Sketch FromExisting(DBSketch sketch, bool isRevitOwned)
185+
{
186+
return new Sketch(sketch)
187+
{
188+
IsRevitOwned = isRevitOwned
189+
};
190+
}
191+
192+
#endregion
193+
}
194+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
using System.Linq;
2+
using Autodesk.DesignScript.Geometry;
3+
using NUnit.Framework;
4+
using Revit.Elements;
5+
using RevitTestServices;
6+
using RTF.Framework;
7+
8+
namespace RevitNodesTests.Elements
9+
{
10+
[TestFixture]
11+
public class SketchTests : RevitNodeTestBase
12+
{
13+
private static PolyCurve MakeSquareOutline(double size = 100)
14+
{
15+
var lines = new[]
16+
{
17+
Line.ByStartPointEndPoint(Point.ByCoordinates(0, 0, 0), Point.ByCoordinates(size, 0, 0)),
18+
Line.ByStartPointEndPoint(Point.ByCoordinates(size, 0, 0), Point.ByCoordinates(size, size, 0)),
19+
Line.ByStartPointEndPoint(Point.ByCoordinates(size, size, 0), Point.ByCoordinates(0, size, 0)),
20+
Line.ByStartPointEndPoint(Point.ByCoordinates(0, size, 0), Point.ByCoordinates(0, 0, 0))
21+
};
22+
return PolyCurve.ByJoinedCurves(lines, 0.001, false);
23+
}
24+
25+
private static Sketch MakeSketchFromPropertyLine(double size = 100)
26+
{
27+
var propertyLine = PropertyLine.ByCurveLoops(MakeSquareOutline(size));
28+
Assume.That(propertyLine, Is.Not.Null, "PropertyLine should be created.");
29+
Assume.That(propertyLine.IsSketchBased, Is.True, "PropertyLine should be sketch-based.");
30+
return propertyLine.Sketch;
31+
}
32+
33+
[Test]
34+
[TestModel(@".\Empty.rvt")]
35+
public void PropertyLineSketch_ReturnsTypedSketchWrapper()
36+
{
37+
var propertyLine = PropertyLine.ByCurveLoops(MakeSquareOutline());
38+
39+
Sketch sketch = propertyLine.Sketch;
40+
41+
Assert.NotNull(sketch);
42+
Assert.IsInstanceOf<Sketch>(sketch);
43+
}
44+
45+
[Test]
46+
[TestModel(@".\Empty.rvt")]
47+
public void Sketch_SketchPlane_ReturnsValidSketchPlane()
48+
{
49+
var sketch = MakeSketchFromPropertyLine();
50+
51+
SketchPlane sketchPlane = sketch.SketchPlane;
52+
53+
Assert.NotNull(sketchPlane);
54+
Assert.NotNull(sketchPlane.Plane);
55+
}
56+
57+
[Test]
58+
[TestModel(@".\Empty.rvt")]
59+
public void Sketch_Owner_ReturnsHostingPropertyLine()
60+
{
61+
var propertyLine = PropertyLine.ByCurveLoops(MakeSquareOutline());
62+
63+
var owner = propertyLine.Sketch.Owner;
64+
65+
Assert.NotNull(owner);
66+
Assert.AreEqual(propertyLine.InternalElement.Id, owner.InternalElement.Id);
67+
}
68+
69+
[Test]
70+
[TestModel(@".\Empty.rvt")]
71+
public void Sketch_Elements_ReturnsAtLeastOneSketchElement()
72+
{
73+
var sketch = MakeSketchFromPropertyLine();
74+
75+
var elements = sketch.Elements;
76+
77+
Assert.NotNull(elements);
78+
Assert.IsTrue(elements.Length > 0,
79+
"A sketch backing a closed-loop PropertyLine should contain at least one model curve.");
80+
Assert.IsTrue(elements.All(e => e != null));
81+
}
82+
83+
[Test]
84+
[TestModel(@".\Empty.rvt")]
85+
public void Sketch_Profile_ReturnsAtLeastOneClosedLoop()
86+
{
87+
var sketch = MakeSketchFromPropertyLine();
88+
89+
PolyCurve[] profile = sketch.Profile;
90+
91+
Assert.NotNull(profile);
92+
Assert.IsTrue(profile.Length > 0, "Profile should contain at least one curve loop.");
93+
Assert.IsTrue(profile.All(p => p != null));
94+
Assert.IsTrue(profile[0].IsClosed,
95+
"The boundary loop of a closed-square PropertyLine should be closed.");
96+
}
97+
}
98+
}

0 commit comments

Comments
 (0)