pi-revit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,256 @@
1
+ using System.IO;
2
+ using System.Text.Json;
3
+ using Autodesk.Revit.DB;
4
+
5
+ namespace RevitBridge.Tools
6
+ {
7
+ /// <summary>
8
+ /// Minimal document export: sheets/views to PDF, DWG, or PNG files, or the
9
+ /// model to IFC. Write = true for the filesystem side effects; only the IFC
10
+ /// branch wraps a Revit transaction (the IFC exporter stores IFC GUID
11
+ /// parameters on exported elements), owned here. Produced files are
12
+ /// discovered by diffing the output directory because Revit appends its own
13
+ /// view/sheet suffixes to multi-file export names.
14
+ /// </summary>
15
+ internal sealed class ExportDocuments : ITool
16
+ {
17
+ private const int MaxIds = 100;
18
+ private const int PngPixelWidth = 2048;
19
+
20
+ public string Name => "export_documents";
21
+ public string Label => "Export Documents";
22
+ public string Description => "Export documents from the open Revit model. format 'pdf'/'dwg'/'png' export the given sheet/view ids to files: pdf combines everything into one file by default (combine=false writes one PDF per sheet/view, named by Revit's naming rule); png renders 2048 px wide; ifc exports the whole model, or just what one given view shows. Files land in output_dir (created if missing; default: a fresh folder under the system temp directory); file_name_prefix sets the base file name (default: the document title; Revit appends view/sheet suffixes for multi-file exports). Returns the produced file paths with sizes. Find sheet/view ids with get_elements (category 'Sheets' or 'Views') first.";
23
+ public bool Write => true;
24
+ public string Tier => "advanced";
25
+
26
+ public object ParametersSchema => new
27
+ {
28
+ type = "object",
29
+ properties = new
30
+ {
31
+ format = new
32
+ {
33
+ type = "string",
34
+ @enum = new[] { "pdf", "dwg", "png", "ifc" },
35
+ description = "Export format. pdf/dwg/png need sheet/view ids; ifc exports the model (optionally filtered to one view).",
36
+ },
37
+ ids = new
38
+ {
39
+ type = "array",
40
+ items = new { type = "integer" },
41
+ description = "Sheet/view element ids to export (1-100). Required for pdf/dwg/png; for ifc at most one view id (default: the whole model).",
42
+ },
43
+ output_dir = new
44
+ {
45
+ type = "string",
46
+ description = "Output directory, created if missing. Default: a fresh folder under the system temp directory.",
47
+ },
48
+ file_name_prefix = new
49
+ {
50
+ type = "string",
51
+ description = "Base file name without extension. Default: the document title.",
52
+ },
53
+ combine = new
54
+ {
55
+ type = "boolean",
56
+ description = "pdf only: combine all sheets/views into one PDF. Default true.",
57
+ },
58
+ },
59
+ required = new[] { "format" },
60
+ };
61
+
62
+ public string? PromptSnippet => "Export sheets/views to PDF, DWG, or PNG files, or the model to IFC.";
63
+ public IReadOnlyList<string>? PromptGuidelines => new[]
64
+ {
65
+ "export_documents needs explicit sheet/view ids for pdf/dwg/png — find them with get_elements (category 'Sheets' or 'Views') first.",
66
+ };
67
+
68
+ public object? Execute(JsonElement args, ToolContext context)
69
+ {
70
+ var doc = context.Document ?? throw new NoActiveDocumentException();
71
+
72
+ string format = (JsonArgs.GetString(args, "format") ?? string.Empty).Trim().ToLowerInvariant();
73
+ if (format is not ("pdf" or "dwg" or "png" or "ifc"))
74
+ throw new ArgumentException($"Unknown format: {JsonArgs.GetString(args, "format")}. Supported: pdf, dwg, png, ifc.");
75
+
76
+ var views = ResolveViews(doc, args, format);
77
+ string outputDir = ResolveOutputDir(args);
78
+ string? prefixInput = JsonArgs.GetString(args, "file_name_prefix");
79
+ string baseName = SanitizeFileName(string.IsNullOrWhiteSpace(prefixInput) ? doc.Title : prefixInput.Trim());
80
+ bool combine = JsonArgs.GetBool(args, "combine", true);
81
+
82
+ // Produced files are found by diffing the directory; the timestamp check
83
+ // (with clock-skew slack) also catches overwrites of pre-existing names.
84
+ DateTime startedUtc = DateTime.UtcNow.AddSeconds(-2);
85
+ var before = new HashSet<string>(Directory.GetFiles(outputDir), StringComparer.OrdinalIgnoreCase);
86
+
87
+ switch (format)
88
+ {
89
+ case "pdf": ExportPdf(doc, views, outputDir, baseName, combine); break;
90
+ case "dwg": ExportDwg(doc, views, outputDir, baseName); break;
91
+ case "png": ExportPng(doc, views, outputDir, baseName); break;
92
+ default: ExportIfc(doc, views, outputDir, baseName); break;
93
+ }
94
+
95
+ var files = Directory.GetFiles(outputDir)
96
+ .Where(path => !before.Contains(path) || File.GetLastWriteTimeUtc(path) >= startedUtc)
97
+ .OrderBy(path => path, StringComparer.OrdinalIgnoreCase)
98
+ .Select(path => new Dictionary<string, object?>
99
+ {
100
+ ["path"] = path,
101
+ ["fileSizeBytes"] = new FileInfo(path).Length,
102
+ })
103
+ .ToList();
104
+ if (files.Count == 0)
105
+ throw new InvalidOperationException($"The {format} export finished but produced no files in {outputDir}.");
106
+
107
+ string sample = string.Join(", ", files.Take(3).Select(file => Path.GetFileName((string)file["path"]!)));
108
+ string compact = $"Exported {files.Count} {format.ToUpperInvariant()} file(s) to {outputDir}: {sample}{(files.Count > 3 ? $" (+{files.Count - 3} more)" : string.Empty)}.";
109
+ return new ToolOutput(new
110
+ {
111
+ format,
112
+ outputDir,
113
+ fileCount = files.Count,
114
+ files,
115
+ }, compact);
116
+ }
117
+
118
+ // ------------------------------------------------------------- format runs
119
+
120
+ private static void ExportPdf(Document doc, List<View> views, string outputDir, string baseName, bool combine)
121
+ {
122
+ var options = new PDFExportOptions
123
+ {
124
+ Combine = combine,
125
+ FileName = baseName, // names the combined file; per-view files follow Revit's naming rule
126
+ };
127
+ if (!Run(() => doc.Export(outputDir, views.Select(view => view.Id).ToList(), options), "PDF"))
128
+ throw new InvalidOperationException("Revit reported a failed PDF export.");
129
+ }
130
+
131
+ private static void ExportDwg(Document doc, List<View> views, string outputDir, string baseName)
132
+ {
133
+ bool available = true;
134
+ try
135
+ {
136
+ available = OptionalFunctionalityUtils.IsDWGExportAvailable();
137
+ }
138
+ catch
139
+ {
140
+ // Probe unavailable; let the export call itself report any failure.
141
+ }
142
+ if (!available)
143
+ throw new InvalidOperationException("DWG export is not available in this Revit installation.");
144
+
145
+ if (!Run(() => doc.Export(outputDir, baseName, views.Select(view => view.Id).ToList(), new DWGExportOptions()), "DWG"))
146
+ throw new InvalidOperationException("Revit reported a failed DWG export.");
147
+ }
148
+
149
+ private static void ExportPng(Document doc, List<View> views, string outputDir, string baseName)
150
+ {
151
+ var options = new ImageExportOptions
152
+ {
153
+ ExportRange = ExportRange.SetOfViews,
154
+ FilePath = Path.Combine(outputDir, baseName),
155
+ HLRandWFViewsFileType = ImageFileType.PNG,
156
+ ShadowViewsFileType = ImageFileType.PNG,
157
+ ImageResolution = ImageResolution.DPI_150,
158
+ ZoomType = ZoomFitType.FitToPage,
159
+ PixelSize = PngPixelWidth,
160
+ FitDirection = FitDirectionType.Horizontal,
161
+ };
162
+ options.SetViewsAndSheets(views.Select(view => view.Id).ToList());
163
+ Run(() => doc.ExportImage(options), "PNG");
164
+ }
165
+
166
+ /// <summary>The IFC exporter writes IFC GUID parameters onto exported elements,
167
+ /// so the Revit API requires a transaction around it — owned here, matching
168
+ /// Revit's own behavior of committing those GUIDs on export.</summary>
169
+ private static void ExportIfc(Document doc, List<View> views, string outputDir, string baseName)
170
+ {
171
+ var options = new IFCExportOptions();
172
+ if (views.Count == 1)
173
+ options.FilterViewId = views[0].Id;
174
+
175
+ using var transaction = new Transaction(doc, "export_documents: ifc");
176
+ if (transaction.Start() != TransactionStatus.Started)
177
+ throw new InvalidOperationException("Unable to start the IFC export transaction.");
178
+ try
179
+ {
180
+ if (!Run(() => doc.Export(outputDir, baseName, options), "IFC"))
181
+ throw new InvalidOperationException("Revit reported a failed IFC export.");
182
+ if (transaction.Commit() != TransactionStatus.Committed)
183
+ throw new InvalidOperationException("The IFC export transaction failed to commit.");
184
+ }
185
+ catch
186
+ {
187
+ if (transaction.GetStatus() == TransactionStatus.Started)
188
+ transaction.RollBack();
189
+ throw;
190
+ }
191
+ }
192
+
193
+ /// <summary>Runs one export call, translating Revit API exceptions into plain errors.</summary>
194
+ private static T Run<T>(Func<T> export, string what)
195
+ {
196
+ try
197
+ {
198
+ return export();
199
+ }
200
+ catch (Autodesk.Revit.Exceptions.ApplicationException ex)
201
+ {
202
+ throw new InvalidOperationException($"Revit could not run the {what} export: {ex.Message}");
203
+ }
204
+ }
205
+
206
+ private static void Run(Action export, string what)
207
+ => Run<object?>(() => { export(); return null; }, what);
208
+
209
+ // ----------------------------------------------------------------- parsing
210
+
211
+ private static List<View> ResolveViews(Document doc, JsonElement args, string format)
212
+ {
213
+ bool present = args.ValueKind == JsonValueKind.Object
214
+ && args.TryGetProperty("ids", out var idsElement)
215
+ && idsElement.ValueKind != JsonValueKind.Null;
216
+ var ids = present ? JsonArgs.GetLongArray(args, "ids") : new List<long>();
217
+
218
+ if (ids.Count > MaxIds)
219
+ throw new ArgumentException($"Too many ids ({ids.Count}); max {MaxIds} per call. Export in batches.");
220
+ if (format != "ifc" && ids.Count == 0)
221
+ throw new ArgumentException($"format '{format}' requires ids of sheets or views. List them with get_elements (category 'Sheets' or 'Views').");
222
+ if (format == "ifc" && ids.Count > 1)
223
+ throw new ArgumentException("format 'ifc' takes at most one view id (the export is filtered to what that view shows); omit ids to export the whole model.");
224
+
225
+ var views = new List<View>(ids.Count);
226
+ foreach (long id in ids.Distinct())
227
+ {
228
+ var view = doc.GetElement(new ElementId(id)) as View
229
+ ?? throw new ArgumentException($"id {id} is not a view or sheet.");
230
+ if (view.IsTemplate)
231
+ throw new ArgumentException($"id {id} ('{view.Name}') is a view template and cannot be exported.");
232
+ if (format == "png" && view is ViewSchedule)
233
+ throw new ArgumentException($"id {id} ('{view.Name}') is a schedule; schedules cannot be exported as PNG images.");
234
+ views.Add(view);
235
+ }
236
+ return views;
237
+ }
238
+
239
+ private static string ResolveOutputDir(JsonElement args)
240
+ {
241
+ string? requested = JsonArgs.GetString(args, "output_dir");
242
+ string outputDir = string.IsNullOrWhiteSpace(requested)
243
+ ? Path.Combine(Path.GetTempPath(), "revit_export_" + Guid.NewGuid().ToString("N"))
244
+ : Path.GetFullPath(requested.Trim());
245
+ Directory.CreateDirectory(outputDir);
246
+ return outputDir;
247
+ }
248
+
249
+ private static string SanitizeFileName(string name)
250
+ {
251
+ var invalid = Path.GetInvalidFileNameChars();
252
+ string clean = new string(name.Select(c => invalid.Contains(c) ? '_' : c).ToArray()).Trim();
253
+ return clean.Length > 0 ? clean : "export";
254
+ }
255
+ }
256
+ }
@@ -0,0 +1,318 @@
1
+ using System.Globalization;
2
+ using System.Text.Json;
3
+ using Autodesk.Revit.DB;
4
+
5
+ namespace RevitBridge.Tools
6
+ {
7
+ internal sealed class GetElementDetails : ITool
8
+ {
9
+ private const int MaxIds = 50;
10
+
11
+ public string Name => "get_element_details";
12
+ public string Label => "Get Element Details";
13
+ public string Description => "Inspect one or more elements by id: parameter VALUES (name, internal value, formatted displayValue, storage type, display unit where applicable, read-only/shared flags), plus optional type parameters, location (point/curve), bounding box, and materials. Internal numeric values are Revit internal units (feet-based); displayValue is formatted in the document's display units and 'unit' names that display unit. The single home for reading parameter values — get_elements only returns identity fields.";
14
+
15
+ public object ParametersSchema => new
16
+ {
17
+ type = "object",
18
+ properties = new
19
+ {
20
+ element_ids = new
21
+ {
22
+ type = "array",
23
+ items = new { type = "integer" },
24
+ description = "Element ids to inspect (1-50 per call).",
25
+ },
26
+ parameter_names = new
27
+ {
28
+ type = "array",
29
+ items = new { type = "string" },
30
+ description = "Only return these parameters (case-insensitive exact names). Default: all.",
31
+ },
32
+ include = new
33
+ {
34
+ type = "object",
35
+ description = "Optional sections per element.",
36
+ properties = new
37
+ {
38
+ parameters = new { type = "boolean", description = "Instance parameter values. Default true." },
39
+ type_parameters = new { type = "boolean", description = "Also include the element type's parameters, marked isType=true. Default false." },
40
+ location = new { type = "boolean", description = "Location point or curve (coordinates in internal feet). Default false." },
41
+ bounding_box = new { type = "boolean", description = "Model bounding box min/max (internal feet). Default false." },
42
+ materials = new { type = "boolean", description = "Material ids/names with area/volume (internal units). Default false." },
43
+ },
44
+ },
45
+ },
46
+ required = new[] { "element_ids" },
47
+ };
48
+
49
+ public string? PromptSnippet => "Read parameter values, location, bounding box, and materials for specific Revit elements.";
50
+ public IReadOnlyList<string>? PromptGuidelines => new[]
51
+ {
52
+ "Use get_element_details (with ids from get_elements or the user's selection) whenever parameter values are needed; numeric values carry both internal value and display value with its unit.",
53
+ };
54
+
55
+ public object? Execute(JsonElement args, ToolContext context)
56
+ {
57
+ var doc = context.Document ?? throw new NoActiveDocumentException();
58
+
59
+ var ids = JsonArgs.GetLongArray(args, "element_ids");
60
+ if (ids.Count == 0)
61
+ throw new ArgumentException("element_ids must contain at least one element id.");
62
+ if (ids.Count > MaxIds)
63
+ throw new ArgumentException($"Too many element_ids ({ids.Count}); max {MaxIds} per call. Inspect ids in batches.");
64
+
65
+ var parameterNames = JsonArgs.GetStringArray(args, "parameter_names");
66
+ HashSet<string>? nameFilter = parameterNames is { Count: > 0 }
67
+ ? new HashSet<string>(parameterNames, StringComparer.OrdinalIgnoreCase)
68
+ : null;
69
+
70
+ JsonElement include = args.TryGetProperty("include", out var includeElement) && includeElement.ValueKind == JsonValueKind.Object
71
+ ? includeElement
72
+ : default;
73
+ bool withParameters = JsonArgs.GetBool(include, "parameters", true);
74
+ bool withTypeParameters = JsonArgs.GetBool(include, "type_parameters", false);
75
+ bool withLocation = JsonArgs.GetBool(include, "location", false);
76
+ bool withBoundingBox = JsonArgs.GetBool(include, "bounding_box", false);
77
+ bool withMaterials = JsonArgs.GetBool(include, "materials", false);
78
+
79
+ var elements = new List<Dictionary<string, object?>>(ids.Count);
80
+ var notFound = new List<long>();
81
+ var compactParts = new List<string>();
82
+
83
+ foreach (long id in ids)
84
+ {
85
+ var element = doc.GetElement(new ElementId(id));
86
+ if (element is null)
87
+ {
88
+ notFound.Add(id);
89
+ continue;
90
+ }
91
+
92
+ var elementType = doc.GetElement(element.GetTypeId());
93
+ var dto = new Dictionary<string, object?>
94
+ {
95
+ ["id"] = id,
96
+ ["name"] = element.Name,
97
+ ["category"] = element.Category?.Name,
98
+ ["typeName"] = elementType?.Name,
99
+ ["typeId"] = elementType?.Id.Value,
100
+ ["levelId"] = element.LevelId is { } levelId && levelId != ElementId.InvalidElementId ? levelId.Value : (long?)null,
101
+ };
102
+
103
+ int parameterCount = 0;
104
+ if (withParameters)
105
+ {
106
+ var parameters = new List<Dictionary<string, object?>>();
107
+ AppendParameters(doc, element, nameFilter, isType: false, parameters);
108
+ if (withTypeParameters && elementType != null)
109
+ AppendParameters(doc, elementType, nameFilter, isType: true, parameters);
110
+ dto["parameters"] = parameters;
111
+ parameterCount = parameters.Count;
112
+ }
113
+
114
+ if (withLocation)
115
+ dto["location"] = DescribeLocation(element);
116
+ if (withBoundingBox)
117
+ dto["boundingBox"] = DescribeBoundingBox(element);
118
+ if (withMaterials)
119
+ dto["materials"] = DescribeMaterials(doc, element);
120
+
121
+ elements.Add(dto);
122
+ compactParts.Add($"'{element.Name}' (id {id}, {element.Category?.Name ?? "no category"}{(withParameters ? $", {parameterCount} params" : string.Empty)})");
123
+ }
124
+
125
+ string compact = $"{elements.Count} element(s)"
126
+ + (compactParts.Count > 0 ? $": {string.Join("; ", compactParts.Take(5))}" : string.Empty)
127
+ + (compactParts.Count > 5 ? $" (+{compactParts.Count - 5} more)" : string.Empty)
128
+ + (notFound.Count > 0 ? $". Not found: {string.Join(", ", notFound)}" : string.Empty)
129
+ + ".";
130
+
131
+ return new ToolOutput(new { count = elements.Count, elements, not_found = notFound }, compact);
132
+ }
133
+
134
+ private static void AppendParameters(Document doc, Element element, HashSet<string>? nameFilter, bool isType, List<Dictionary<string, object?>> target)
135
+ {
136
+ var rows = new List<Dictionary<string, object?>>();
137
+ foreach (Parameter parameter in element.Parameters)
138
+ {
139
+ string name = parameter.Definition?.Name ?? string.Empty;
140
+ if (nameFilter != null && !nameFilter.Contains(name))
141
+ continue;
142
+ rows.Add(DescribeParameter(doc, parameter, name, isType));
143
+ }
144
+ rows.Sort((a, b) => string.Compare(a["name"] as string, b["name"] as string, StringComparison.OrdinalIgnoreCase));
145
+ target.AddRange(rows);
146
+ }
147
+
148
+ private static Dictionary<string, object?> DescribeParameter(Document doc, Parameter parameter, string name, bool isType)
149
+ {
150
+ bool hasValue = parameter.HasValue;
151
+ var row = new Dictionary<string, object?>
152
+ {
153
+ ["name"] = name,
154
+ ["storageType"] = parameter.StorageType.ToString(),
155
+ ["isType"] = isType,
156
+ ["isReadOnly"] = parameter.IsReadOnly,
157
+ };
158
+
159
+ switch (parameter.StorageType)
160
+ {
161
+ case StorageType.String:
162
+ {
163
+ string? text = hasValue ? parameter.AsString() : null;
164
+ row["value"] = text;
165
+ row["displayValue"] = text;
166
+ break;
167
+ }
168
+ case StorageType.Integer:
169
+ row["value"] = hasValue ? parameter.AsInteger() : (int?)null;
170
+ row["displayValue"] = hasValue
171
+ ? SafeValueString(parameter) ?? parameter.AsInteger().ToString(CultureInfo.InvariantCulture)
172
+ : null;
173
+ break;
174
+ case StorageType.Double:
175
+ {
176
+ row["value"] = hasValue ? parameter.AsDouble() : (double?)null;
177
+ row["displayValue"] = hasValue ? SafeValueString(parameter) : null;
178
+ try
179
+ {
180
+ var spec = parameter.Definition?.GetDataType();
181
+ if (spec != null && UnitUtils.IsMeasurableSpec(spec))
182
+ row["unit"] = UnitResolver.ShortName(doc.GetUnits().GetFormatOptions(spec).GetUnitTypeId());
183
+ }
184
+ catch
185
+ {
186
+ // Leave unit out when the spec or format options are unavailable.
187
+ }
188
+ break;
189
+ }
190
+ case StorageType.ElementId:
191
+ {
192
+ var referenced = hasValue ? parameter.AsElementId() : null;
193
+ row["value"] = referenced != null && referenced != ElementId.InvalidElementId ? referenced.Value : (long?)null;
194
+ row["displayValue"] = hasValue
195
+ ? SafeValueString(parameter) ?? (referenced != null ? doc.GetElement(referenced)?.Name : null)
196
+ : null;
197
+ break;
198
+ }
199
+ default:
200
+ row["value"] = null;
201
+ row["displayValue"] = hasValue ? SafeValueString(parameter) : null;
202
+ break;
203
+ }
204
+
205
+ if (parameter.IsShared)
206
+ {
207
+ row["isShared"] = true;
208
+ try
209
+ {
210
+ row["guid"] = parameter.GUID.ToString();
211
+ }
212
+ catch
213
+ {
214
+ // GUID can throw for malformed shared parameters; skip it.
215
+ }
216
+ }
217
+ if (parameter.Definition is InternalDefinition internalDefinition && internalDefinition.BuiltInParameter != BuiltInParameter.INVALID)
218
+ row["builtInParameter"] = internalDefinition.BuiltInParameter.ToString();
219
+
220
+ return row;
221
+ }
222
+
223
+ private static string? SafeValueString(Parameter parameter)
224
+ {
225
+ try
226
+ {
227
+ return parameter.AsValueString();
228
+ }
229
+ catch
230
+ {
231
+ return null;
232
+ }
233
+ }
234
+
235
+ private static object? DescribeLocation(Element element)
236
+ {
237
+ switch (element.Location)
238
+ {
239
+ case LocationPoint point:
240
+ {
241
+ var dto = new Dictionary<string, object?> { ["kind"] = "point", ["point"] = Point(point.Point) };
242
+ try
243
+ {
244
+ dto["rotation"] = point.Rotation;
245
+ }
246
+ catch
247
+ {
248
+ // Not all point-located elements expose a rotation.
249
+ }
250
+ return dto;
251
+ }
252
+ case LocationCurve curve:
253
+ {
254
+ var c = curve.Curve;
255
+ var dto = new Dictionary<string, object?> { ["kind"] = "curve", ["isLine"] = c is Line };
256
+ if (c.IsBound)
257
+ {
258
+ dto["start"] = Point(c.GetEndPoint(0));
259
+ dto["end"] = Point(c.GetEndPoint(1));
260
+ dto["length"] = c.Length;
261
+ }
262
+ return dto;
263
+ }
264
+ default:
265
+ return null;
266
+ }
267
+ }
268
+
269
+ private static object? DescribeBoundingBox(Element element)
270
+ {
271
+ var box = element.get_BoundingBox(null);
272
+ return box is null ? null : new { min = Point(box.Min), max = Point(box.Max) };
273
+ }
274
+
275
+ private static List<Dictionary<string, object?>> DescribeMaterials(Document doc, Element element)
276
+ {
277
+ var result = new List<Dictionary<string, object?>>();
278
+ ICollection<ElementId> materialIds;
279
+ try
280
+ {
281
+ materialIds = element.GetMaterialIds(false);
282
+ }
283
+ catch
284
+ {
285
+ return result;
286
+ }
287
+
288
+ foreach (var materialId in materialIds)
289
+ {
290
+ var row = new Dictionary<string, object?>
291
+ {
292
+ ["id"] = materialId.Value,
293
+ ["name"] = doc.GetElement(materialId)?.Name,
294
+ };
295
+ try
296
+ {
297
+ row["area"] = element.GetMaterialArea(materialId, false);
298
+ }
299
+ catch
300
+ {
301
+ // Area is undefined for some element kinds.
302
+ }
303
+ try
304
+ {
305
+ row["volume"] = element.GetMaterialVolume(materialId);
306
+ }
307
+ catch
308
+ {
309
+ // Volume is undefined for some element kinds.
310
+ }
311
+ result.Add(row);
312
+ }
313
+ return result;
314
+ }
315
+
316
+ private static object Point(XYZ point) => new { x = point.X, y = point.Y, z = point.Z };
317
+ }
318
+ }