pi-revit 0.3.1 → 0.4.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.
- package/CHANGELOG.md +388 -351
- package/README.md +426 -22
- package/extensions/pi-revit/index.ts +246 -187
- package/extensions/pi-revit/instance-router.ts +86 -0
- package/extensions/pi-revit/script-library.ts +146 -0
- package/extensions/pi-revit/tool-catalog.ts +67 -0
- package/package.json +59 -59
- package/skills/pi-revit/SKILL.md +196 -32
- package/skills/pi-revit/references/model-audit-export.md +29 -0
- package/skills/pi-revit/references/room-documentation.md +28 -0
- package/src/Revit/BridgeServer.cs +87 -37
- package/src/Revit/OperationStore.cs +178 -0
- package/src/Revit/ToolRegistry.cs +57 -35
- package/src/Revit/Tools/CaptureView.cs +2 -1
- package/src/Revit/Tools/ChangeElementTypes.cs +60 -0
- package/src/Revit/Tools/CreateTags.cs +95 -0
- package/src/Revit/Tools/DeleteElements.cs +44 -0
- package/src/Revit/Tools/DocumentGuard.cs +64 -64
- package/src/Revit/Tools/ElementQueryScope.cs +27 -0
- package/src/Revit/Tools/ExecuteCsharp.cs +45 -35
- package/src/Revit/Tools/ExportDocuments.cs +121 -120
- package/src/Revit/Tools/FailureGuard.cs +26 -26
- package/src/Revit/Tools/GetElementDetails.cs +42 -12
- package/src/Revit/Tools/GetElementRelationships.cs +76 -0
- package/src/Revit/Tools/GetElements.cs +37 -23
- package/src/Revit/Tools/GetLinkedElements.cs +82 -0
- package/src/Revit/Tools/GetLinkedModels.cs +66 -0
- package/src/Revit/Tools/GetModelCoordinates.cs +49 -0
- package/src/Revit/Tools/GetModelOverview.cs +2 -2
- package/src/Revit/Tools/GetScheduleFields.cs +37 -0
- package/src/Revit/Tools/GetSchedules.cs +89 -0
- package/src/Revit/Tools/ManageElementSets.cs +106 -0
- package/src/Revit/Tools/ManageSchedules.cs +164 -0
- package/src/Revit/Tools/ManageSelection.cs +37 -36
- package/src/Revit/Tools/ManageSheetPlacements.cs +97 -0
- package/src/Revit/Tools/ManageSheets.cs +63 -0
- package/src/Revit/Tools/ManageViews.cs +100 -0
- package/src/Revit/Tools/MeasureGeometry.cs +54 -0
- package/src/Revit/Tools/ModelEditBatch.cs +102 -0
- package/src/Revit/Tools/ModelEditInputs.cs +49 -0
- package/src/Revit/Tools/OpenView.cs +2 -1
- package/src/Revit/Tools/QuerySpatialElements.cs +63 -0
- package/src/Revit/Tools/SetParameters.cs +43 -93
- package/src/Revit/Tools/SpatialBounds.cs +30 -0
- package/src/Revit/Tools/SummarizeElements.cs +87 -0
- package/src/Revit/Tools/ToolSupport.cs +1 -1
- package/src/Revit/Tools/TransformElements.cs +58 -0
- package/workspace/AGENTS.md +45 -45
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
using System.Text.Json;
|
|
2
|
+
using Autodesk.Revit.DB;
|
|
3
|
+
|
|
4
|
+
namespace RevitBridge.Tools;
|
|
5
|
+
|
|
6
|
+
internal sealed class ManageViews : ITool
|
|
7
|
+
{
|
|
8
|
+
public string Name => "manage_views";
|
|
9
|
+
public string Label => "Manage Views";
|
|
10
|
+
public string Tier => "advanced";
|
|
11
|
+
public bool Write => true;
|
|
12
|
+
public string Description => "Create a plan, isometric 3D view or section; duplicate or update an existing view. Resolve view-family-type IDs with get_element_types (of_class ViewFamilyType), and level IDs with get_elements. A section uses document internal axes and explicit length units, orthogonal viewing_direction/up vectors, origin, width/height/depth. Duplication options are duplicate, with_detailing or dependent. Optional name, scale and view_template_id apply in the same transaction; -1 removes a template. Incompatible templates or template-controlled scale changes fail without removing the template. preview=true commit-validates then rolls back; created preview IDs are temporary. Use delete_elements to remove views.";
|
|
13
|
+
public object ParametersSchema => new
|
|
14
|
+
{
|
|
15
|
+
type = "object", properties = new
|
|
16
|
+
{
|
|
17
|
+
action = new { type = "string", @enum = new[] { "create_plan", "create_3d", "create_section", "duplicate", "update" } },
|
|
18
|
+
view_id = new { type = "integer", description = "Existing view for duplicate/update." },
|
|
19
|
+
view_family_type_id = new { type = "integer", description = "Compatible ViewFamilyType for creation." },
|
|
20
|
+
level_id = new { type = "integer", description = "Level for create_plan." },
|
|
21
|
+
duplicate_option = new { type = "string", @enum = new[] { "duplicate", "with_detailing", "dependent" } },
|
|
22
|
+
name = new { type = "string", minLength = 1 }, scale = new { type = "integer", minimum = 1, maximum = 24000 },
|
|
23
|
+
view_template_id = new { type = "integer", description = "Compatible template ID, or -1 to remove." },
|
|
24
|
+
unit = ModelEditInputs.LengthUnitSchema,
|
|
25
|
+
origin = ModelEditInputs.VectorSchema("Section origin in document internal coordinates, in unit."),
|
|
26
|
+
viewing_direction = ModelEditInputs.VectorSchema("Section viewing direction, dimensionless nonzero vector."),
|
|
27
|
+
up = ModelEditInputs.VectorSchema("Section up vector, orthogonal to viewing_direction."),
|
|
28
|
+
width = new { type = "number", exclusiveMinimum = 0 }, height = new { type = "number", exclusiveMinimum = 0 }, depth = new { type = "number", exclusiveMinimum = 0 },
|
|
29
|
+
preview = ModelEditInputs.PreviewSchema,
|
|
30
|
+
}, required = new[] { "action" },
|
|
31
|
+
};
|
|
32
|
+
public object Execute(JsonElement args, ToolContext context)
|
|
33
|
+
{
|
|
34
|
+
var doc = context.Document ?? throw new NoActiveDocumentException();
|
|
35
|
+
string action = JsonArgs.GetString(args, "action") ?? "";
|
|
36
|
+
if (action is not ("create_plan" or "create_3d" or "create_section" or "duplicate" or "update")) throw new ArgumentException("Unknown view action.");
|
|
37
|
+
return ModelEditBatch.Run(doc, Name, args, new[] { new ModelEditBatch.Step(new() { ["action"] = action }, () =>
|
|
38
|
+
{
|
|
39
|
+
ElementId Id(string key) => new(JsonArgs.GetLong(args, key) is long id && id > 0 ? id : throw new ArgumentException($"{key} must be a positive ID."));
|
|
40
|
+
View view;
|
|
41
|
+
object? before = null;
|
|
42
|
+
if (action is "duplicate" or "update")
|
|
43
|
+
{
|
|
44
|
+
view = doc.GetElement(Id("view_id")) as View ?? throw new ArgumentException("view_id is not a view.");
|
|
45
|
+
before = Describe(view);
|
|
46
|
+
if (action == "duplicate")
|
|
47
|
+
{
|
|
48
|
+
var option = (JsonArgs.GetString(args, "duplicate_option") ?? "duplicate") switch
|
|
49
|
+
{
|
|
50
|
+
"duplicate" => ViewDuplicateOption.Duplicate, "with_detailing" => ViewDuplicateOption.WithDetailing,
|
|
51
|
+
"dependent" => ViewDuplicateOption.AsDependent, _ => throw new ArgumentException("Invalid duplicate_option."),
|
|
52
|
+
};
|
|
53
|
+
if (!view.CanViewBeDuplicated(option)) throw new ArgumentException("This view cannot be duplicated with that option.");
|
|
54
|
+
view = (View)doc.GetElement(view.Duplicate(option));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
else if (action == "create_plan") view = ViewPlan.Create(doc, Id("view_family_type_id"), Id("level_id"));
|
|
58
|
+
else if (action == "create_3d") view = View3D.CreateIsometric(doc, Id("view_family_type_id"));
|
|
59
|
+
else
|
|
60
|
+
{
|
|
61
|
+
double factor = ModelEditInputs.LengthScale(args);
|
|
62
|
+
var origin = ModelEditInputs.Vector(args, "origin").Multiply(factor);
|
|
63
|
+
var direction = ModelEditInputs.Vector(args, "viewing_direction");
|
|
64
|
+
var up = ModelEditInputs.Vector(args, "up");
|
|
65
|
+
if (direction.GetLength() < 1e-12 || up.GetLength() < 1e-12) throw new ArgumentException("Section directions must be nonzero.");
|
|
66
|
+
direction = direction.Normalize(); up = up.Normalize();
|
|
67
|
+
if (Math.Abs(direction.DotProduct(up)) > 1e-8) throw new ArgumentException("Section up and viewing_direction must be orthogonal.");
|
|
68
|
+
double width = ModelEditInputs.Number(args, "width") * factor, height = ModelEditInputs.Number(args, "height") * factor, depth = ModelEditInputs.Number(args, "depth") * factor;
|
|
69
|
+
if (width <= 1e-6 || height <= 1e-6 || depth <= 1e-6) throw new ArgumentException("Section dimensions must exceed 0.000001 feet.");
|
|
70
|
+
var transform = Transform.Identity;
|
|
71
|
+
transform.Origin = origin; transform.BasisZ = direction; transform.BasisY = up; transform.BasisX = up.CrossProduct(direction).Normalize();
|
|
72
|
+
using var box = new BoundingBoxXYZ { Transform = transform, Min = new XYZ(-width / 2, -height / 2, 0), Max = new XYZ(width / 2, height / 2, depth), Enabled = true };
|
|
73
|
+
view = ViewSection.CreateSection(doc, Id("view_family_type_id"), box);
|
|
74
|
+
}
|
|
75
|
+
if (args.TryGetProperty("name", out var name))
|
|
76
|
+
{
|
|
77
|
+
if (name.ValueKind != JsonValueKind.String || string.IsNullOrWhiteSpace(name.GetString())) throw new ArgumentException("name must be nonempty.");
|
|
78
|
+
view.Name = name.GetString()!;
|
|
79
|
+
}
|
|
80
|
+
if (args.TryGetProperty("view_template_id", out _))
|
|
81
|
+
{
|
|
82
|
+
long template = JsonArgs.GetLong(args, "view_template_id") ?? 0;
|
|
83
|
+
if (template != -1 && (template <= 0 || !view.IsValidViewTemplate(new ElementId(template)))) throw new ArgumentException("view_template_id is not compatible with this view.");
|
|
84
|
+
view.ViewTemplateId = new ElementId(template);
|
|
85
|
+
}
|
|
86
|
+
if (args.TryGetProperty("scale", out var scale))
|
|
87
|
+
{
|
|
88
|
+
if (!scale.TryGetInt32(out int value) || value is < 1 or > 24000) throw new ArgumentException("scale must be an integer from 1 to 24000.");
|
|
89
|
+
var parameter = view.get_Parameter(BuiltInParameter.VIEW_SCALE);
|
|
90
|
+
if (parameter == null) throw new ArgumentException("This view's scale is unavailable.");
|
|
91
|
+
if (doc.GetElement(view.ViewTemplateId) is View template && template.GetTemplateParameterIds().Except(template.GetNonControlledTemplateParameterIds()).Contains(parameter.Id))
|
|
92
|
+
throw new ArgumentException("This view's scale is controlled by its template.");
|
|
93
|
+
view.Scale = value;
|
|
94
|
+
}
|
|
95
|
+
doc.Regenerate();
|
|
96
|
+
return new() { ["before"] = before, ["view"] = Describe(view), ["created"] = action != "update", ["id_is_temporary"] = action != "update" && JsonArgs.GetBool(args, "preview", false) };
|
|
97
|
+
}) }).Payload;
|
|
98
|
+
}
|
|
99
|
+
private static object Describe(View view) => new { id = view.Id.Value, unique_id = view.UniqueId, name = view.Name, view_type = view.ViewType.ToString(), template_id = view.ViewTemplateId.Value, scale = view.Scale };
|
|
100
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
using System.Text.Json;
|
|
2
|
+
using Autodesk.Revit.DB;
|
|
3
|
+
|
|
4
|
+
namespace RevitBridge.Tools;
|
|
5
|
+
|
|
6
|
+
internal sealed class MeasureGeometry : ITool
|
|
7
|
+
{
|
|
8
|
+
public string Name => "measure_geometry";
|
|
9
|
+
public string Label => "Measure Geometry";
|
|
10
|
+
public string Tier => "advanced";
|
|
11
|
+
public string Description => "Measure exact Euclidean distance between two explicit points, or approximate separation between two host elements' model axis-aligned bounding boxes. Required unit applies to point inputs, optional clearance threshold, and all output coordinates/distances. Points use document internal axes. Box gap is a lower bound on geometry separation: zero means enclosing boxes touch/overlap, not a confirmed clash. Bounds can include nonphysical geometry. An optional clearance threshold classifies only box proximity, never a physical clearance failure. Does not traverse linked contents.";
|
|
12
|
+
public object ParametersSchema => new
|
|
13
|
+
{
|
|
14
|
+
type = "object", properties = new
|
|
15
|
+
{
|
|
16
|
+
mode = new { type = "string", @enum = new[] { "point_distance", "bounding_box_gap" } }, unit = ModelEditInputs.LengthUnitSchema,
|
|
17
|
+
point_a = ModelEditInputs.VectorSchema("First point in document internal axes, in unit."), point_b = ModelEditInputs.VectorSchema("Second point in document internal axes, in unit."),
|
|
18
|
+
element_a_id = new { type = "integer", minimum = 1 }, element_b_id = new { type = "integer", minimum = 1 },
|
|
19
|
+
clearance = new { type = "number", minimum = 0, description = "Optional box-proximity threshold in unit, only for bounding_box_gap." },
|
|
20
|
+
}, required = new[] { "mode", "unit" },
|
|
21
|
+
};
|
|
22
|
+
public object Execute(JsonElement args, ToolContext context)
|
|
23
|
+
{
|
|
24
|
+
var doc = context.Document ?? throw new NoActiveDocumentException();
|
|
25
|
+
double scale = ModelEditInputs.LengthScale(args);
|
|
26
|
+
string? mode = JsonArgs.GetString(args, "mode"), unit = JsonArgs.GetString(args, "unit");
|
|
27
|
+
if (mode == "point_distance")
|
|
28
|
+
{
|
|
29
|
+
if (args.TryGetProperty("clearance", out _) || args.TryGetProperty("element_a_id", out _) || args.TryGetProperty("element_b_id", out _))
|
|
30
|
+
throw new ArgumentException("point_distance accepts points, not element IDs or clearance.");
|
|
31
|
+
var a = ModelEditInputs.Vector(args, "point_a").Multiply(scale); var b = ModelEditInputs.Vector(args, "point_b").Multiply(scale);
|
|
32
|
+
return new { method = mode, approximate = false, coordinate_system = "document_internal", unit, distance = a.DistanceTo(b) / scale,
|
|
33
|
+
delta = SpatialBounds.Coordinates(b - a, scale), point_a = SpatialBounds.Coordinates(a, scale), point_b = SpatialBounds.Coordinates(b, scale) };
|
|
34
|
+
}
|
|
35
|
+
if (mode != "bounding_box_gap") throw new ArgumentException("Unknown measurement mode.");
|
|
36
|
+
if (args.TryGetProperty("point_a", out _) || args.TryGetProperty("point_b", out _)) throw new ArgumentException("bounding_box_gap accepts element IDs, not points.");
|
|
37
|
+
Element Resolve(string key)
|
|
38
|
+
{
|
|
39
|
+
long id = JsonArgs.GetLong(args, key) ?? 0;
|
|
40
|
+
return id > 0 ? doc.GetElement(new ElementId(id)) ?? throw new ArgumentException($"{key} {id} was not found.") : throw new ArgumentException($"{key} must be positive.");
|
|
41
|
+
}
|
|
42
|
+
var elementA = Resolve("element_a_id"); var elementB = Resolve("element_b_id");
|
|
43
|
+
var boundsA = SpatialBounds.Of(elementA) ?? throw new ArgumentException($"Element {elementA.Id.Value} has no model bounding box.");
|
|
44
|
+
var boundsB = SpatialBounds.Of(elementB) ?? throw new ArgumentException($"Element {elementB.Id.Value} has no model bounding box.");
|
|
45
|
+
var gap = boundsA.Gap(boundsB); double distance = gap.GetLength() / scale;
|
|
46
|
+
double? clearance = args.TryGetProperty("clearance", out _) ? ModelEditInputs.Number(args, "clearance") : null;
|
|
47
|
+
if (clearance < 0) throw new ArgumentException("clearance cannot be negative.");
|
|
48
|
+
return new { method = mode, approximate = true, coordinate_system = "document_internal", unit, distance, axis_gaps = SpatialBounds.Coordinates(gap, scale),
|
|
49
|
+
boxes_overlap_or_touch = boundsA.Intersects(boundsB), clearance, box_gap_below_clearance = clearance.HasValue ? (bool?)(distance < clearance.Value) : null,
|
|
50
|
+
interpretation = "Bounding-box gap is a lower bound on geometry separation. Overlap or a gap below clearance identifies candidates for geometry review, not a confirmed clash or clearance failure.",
|
|
51
|
+
element_a = new { id = elementA.Id.Value, unique_id = elementA.UniqueId, bounds = boundsA.Describe(scale) },
|
|
52
|
+
element_b = new { id = elementB.Id.Value, unique_id = elementB.UniqueId, bounds = boundsB.Describe(scale) } };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
using System.Text.Json;
|
|
2
|
+
using Autodesk.Revit.DB;
|
|
3
|
+
|
|
4
|
+
namespace RevitBridge.Tools;
|
|
5
|
+
|
|
6
|
+
/// <summary>Runs model-only edits. Files, UI actions and arbitrary scripts must not use this preview contract.</summary>
|
|
7
|
+
internal static class ModelEditBatch
|
|
8
|
+
{
|
|
9
|
+
internal sealed record Step(Dictionary<string, object?> Target, Func<Dictionary<string, object?>> Apply);
|
|
10
|
+
|
|
11
|
+
internal sealed record Result(bool Committed, bool Preview, bool Atomic, bool CommitValidationPerformed,
|
|
12
|
+
IReadOnlyList<Dictionary<string, object?>> Succeeded, IReadOnlyList<Dictionary<string, object?>> Proposed,
|
|
13
|
+
IReadOnlyList<Dictionary<string, object?>> Failed, IReadOnlyList<string> Warnings)
|
|
14
|
+
{
|
|
15
|
+
public object Payload => new
|
|
16
|
+
{
|
|
17
|
+
updated = Committed ? Succeeded.Count : 0, committed = Committed, preview = Preview, atomic = Atomic,
|
|
18
|
+
succeeded = Succeeded, proposed = Proposed, failed = Failed, commitWarnings = Warnings,
|
|
19
|
+
commit_validation_performed = CommitValidationPerformed,
|
|
20
|
+
validation = Preview ? (CommitValidationPerformed ? "Revit commit validation completed; preview group rollback confirmed." : "Preview group rollback confirmed. Commit-time validation was not performed because the batch was rejected before commit.") : null,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public static Result Run(Document doc, string name, JsonElement args, IReadOnlyList<Step> steps)
|
|
25
|
+
{
|
|
26
|
+
foreach (string flag in new[] { "preview", "atomic" })
|
|
27
|
+
if (args.TryGetProperty(flag, out var value) && value.ValueKind is not (JsonValueKind.True or JsonValueKind.False))
|
|
28
|
+
throw new ArgumentException($"{flag} must be a boolean; no edits were attempted.");
|
|
29
|
+
bool preview = JsonArgs.GetBool(args, "preview", false);
|
|
30
|
+
bool atomic = JsonArgs.GetBool(args, "atomic", false);
|
|
31
|
+
var accepted = new List<Dictionary<string, object?>>();
|
|
32
|
+
var failed = new List<Dictionary<string, object?>>();
|
|
33
|
+
using var group = preview ? new TransactionGroup(doc, name + " preview") : null;
|
|
34
|
+
using var transaction = new Transaction(doc, name);
|
|
35
|
+
try
|
|
36
|
+
{
|
|
37
|
+
if (group != null && group.Start() != TransactionStatus.Started) throw new InvalidOperationException("Could not start preview transaction group.");
|
|
38
|
+
if (transaction.Start() != TransactionStatus.Started) throw new InvalidOperationException("Could not start model transaction.");
|
|
39
|
+
var failures = FailureGuard.Attach(transaction);
|
|
40
|
+
for (int index = 0; index < steps.Count; index++)
|
|
41
|
+
{
|
|
42
|
+
var step = steps[index];
|
|
43
|
+
using var part = new SubTransaction(doc);
|
|
44
|
+
if (part.Start() != TransactionStatus.Started) throw new InvalidOperationException("Could not start edit subtransaction.");
|
|
45
|
+
Dictionary<string, object?> change;
|
|
46
|
+
try
|
|
47
|
+
{
|
|
48
|
+
change = step.Apply();
|
|
49
|
+
if (part.Commit() != TransactionStatus.Committed || part.GetStatus() != TransactionStatus.Committed)
|
|
50
|
+
throw new InvalidOperationException("Edit subtransaction did not commit.");
|
|
51
|
+
}
|
|
52
|
+
catch (Autodesk.Revit.Exceptions.RegenerationFailedException) { throw; }
|
|
53
|
+
catch (Exception error)
|
|
54
|
+
{
|
|
55
|
+
if (part.GetStatus() == TransactionStatus.Started) part.RollBack();
|
|
56
|
+
if (part.GetStatus() != TransactionStatus.RolledBack)
|
|
57
|
+
throw new InvalidOperationException("Edit rollback could not be confirmed; stopping the batch.", error);
|
|
58
|
+
var rejection = new Dictionary<string, object?>(step.Target) { ["index"] = index, ["reason"] = error.Message };
|
|
59
|
+
failed.Add(rejection);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
var row = new Dictionary<string, object?>(step.Target) { ["index"] = index };
|
|
63
|
+
foreach (var field in change) row[field.Key] = field.Value;
|
|
64
|
+
accepted.Add(row);
|
|
65
|
+
}
|
|
66
|
+
bool commit = accepted.Count > 0 && !(atomic && failed.Count > 0);
|
|
67
|
+
if (commit)
|
|
68
|
+
{
|
|
69
|
+
var status = transaction.Commit();
|
|
70
|
+
if (status != TransactionStatus.Committed || transaction.GetStatus() != TransactionStatus.Committed)
|
|
71
|
+
throw new InvalidOperationException($"Commit returned {status}; final status {transaction.GetStatus()}." + failures.DescribeErrors());
|
|
72
|
+
}
|
|
73
|
+
else
|
|
74
|
+
{
|
|
75
|
+
var status = transaction.RollBack();
|
|
76
|
+
if (status != TransactionStatus.RolledBack || transaction.GetStatus() != TransactionStatus.RolledBack)
|
|
77
|
+
throw new InvalidOperationException("Batch rollback could not be confirmed.");
|
|
78
|
+
}
|
|
79
|
+
if (group != null) RollBackGroup(group);
|
|
80
|
+
bool committed = commit && !preview;
|
|
81
|
+
return new Result(committed, preview, atomic, commit,
|
|
82
|
+
committed ? accepted : new List<Dictionary<string, object?>>(),
|
|
83
|
+
committed ? new List<Dictionary<string, object?>>() : accepted, failed, failures.Warnings);
|
|
84
|
+
}
|
|
85
|
+
catch (Exception error)
|
|
86
|
+
{
|
|
87
|
+
string cleanup = FailureGuard.RollBackAndDescribe(transaction);
|
|
88
|
+
if (group != null)
|
|
89
|
+
{
|
|
90
|
+
try { RollBackGroup(group); cleanup += " Preview group rollback confirmed."; }
|
|
91
|
+
catch (Exception rollback) { cleanup += " Preview group rollback not confirmed: " + rollback.Message; }
|
|
92
|
+
}
|
|
93
|
+
throw new InvalidOperationException(error.Message + " " + cleanup, error);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private static void RollBackGroup(TransactionGroup group)
|
|
98
|
+
{
|
|
99
|
+
if (group.GetStatus() == TransactionStatus.Started) group.RollBack();
|
|
100
|
+
if (group.GetStatus() != TransactionStatus.RolledBack) throw new InvalidOperationException($"Preview group status is {group.GetStatus()}.");
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
using System.Text.Json;
|
|
2
|
+
using Autodesk.Revit.DB;
|
|
3
|
+
|
|
4
|
+
namespace RevitBridge.Tools;
|
|
5
|
+
|
|
6
|
+
internal static class ModelEditInputs
|
|
7
|
+
{
|
|
8
|
+
public static object IdsSchema => new { type = "array", minItems = 1, maxItems = 200, uniqueItems = true, items = new { type = "integer", minimum = 1 } };
|
|
9
|
+
public static object PreviewSchema => new { type = "boolean", description = "Commit-validate then roll back all model edits. Default false." };
|
|
10
|
+
public static object LengthUnitSchema => new { type = "string", @enum = new[] { "millimeters", "centimeters", "meters", "feet", "inches" } };
|
|
11
|
+
public static object VectorSchema(string description) => new { type = "array", minItems = 3, maxItems = 3, items = new { type = "number" }, description };
|
|
12
|
+
public static List<ElementId> Ids(JsonElement args)
|
|
13
|
+
{
|
|
14
|
+
var ids = JsonArgs.GetLongArray(args, "element_ids");
|
|
15
|
+
if (ids.Count is < 1 or > 200 || ids.Any(id => id <= 0) || ids.Distinct().Count() != ids.Count)
|
|
16
|
+
throw new ArgumentException("element_ids must contain 1–200 distinct positive IDs.");
|
|
17
|
+
return ids.Select(id => new ElementId(id)).ToList();
|
|
18
|
+
}
|
|
19
|
+
public static double LengthScale(JsonElement args) => JsonArgs.GetString(args, "unit") switch
|
|
20
|
+
{
|
|
21
|
+
"millimeters" => 1 / 304.8, "centimeters" => 1 / 30.48, "meters" => 1 / 0.3048,
|
|
22
|
+
"feet" => 1, "inches" => 1 / 12.0,
|
|
23
|
+
_ => throw new ArgumentException("unit must be millimeters, centimeters, meters, feet or inches."),
|
|
24
|
+
};
|
|
25
|
+
public static double Number(JsonElement args, string name)
|
|
26
|
+
{
|
|
27
|
+
if (!args.TryGetProperty(name, out var value) || value.ValueKind != JsonValueKind.Number || !value.TryGetDouble(out double number) || !double.IsFinite(number))
|
|
28
|
+
throw new ArgumentException($"{name} must be a finite number.");
|
|
29
|
+
return number;
|
|
30
|
+
}
|
|
31
|
+
public static XYZ Vector(JsonElement args, string name)
|
|
32
|
+
{
|
|
33
|
+
if (!args.TryGetProperty(name, out var values) || values.ValueKind != JsonValueKind.Array || values.GetArrayLength() != 3)
|
|
34
|
+
throw new ArgumentException($"{name} must contain exactly three finite numbers.");
|
|
35
|
+
var numbers = values.EnumerateArray().Select(v => v.ValueKind == JsonValueKind.Number && v.TryGetDouble(out double n) && double.IsFinite(n) ? n : throw new ArgumentException($"{name} must contain finite numbers.")).ToArray();
|
|
36
|
+
return new XYZ(numbers[0], numbers[1], numbers[2]);
|
|
37
|
+
}
|
|
38
|
+
public static object Snapshot(Element element)
|
|
39
|
+
{
|
|
40
|
+
static double[] Point(XYZ p) => new[] { p.X, p.Y, p.Z };
|
|
41
|
+
object? location = element.Location switch
|
|
42
|
+
{
|
|
43
|
+
LocationPoint p => new { kind = "point", point = Point(p.Point) },
|
|
44
|
+
LocationCurve c when c.Curve.IsBound => new { kind = "curve_endpoints", start = Point(c.Curve.GetEndPoint(0)), end = Point(c.Curve.GetEndPoint(1)) } as object,
|
|
45
|
+
_ => null,
|
|
46
|
+
};
|
|
47
|
+
return new { id = element.Id.Value, unique_id = element.UniqueId, name = element.Name, type_id = element.GetTypeId().Value, location };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -15,7 +15,8 @@ namespace RevitBridge.Tools
|
|
|
15
15
|
public string Name => "open_view";
|
|
16
16
|
public string Label => "Open View";
|
|
17
17
|
public string Description => "Activate a view or sheet in the Revit UI, like double-clicking it in the Project Browser. Identify the target by view_id (an id from get_elements category Views/Sheets or from view/sheet creation) or by name (exact view name, sheet number like 'A-101', or sheet number - name; case-insensitive). Activation is queued and completes the instant this call returns control to Revit — a capture_view immediately after may still show the previous active view. View templates and internal views cannot be opened.";
|
|
18
|
-
public bool Write => false;
|
|
18
|
+
public bool Write => false;
|
|
19
|
+
public IReadOnlyList<string> Effects => new[] { "ui" };
|
|
19
20
|
|
|
20
21
|
public object ParametersSchema => new
|
|
21
22
|
{
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
using System.Text.Json;
|
|
2
|
+
using Autodesk.Revit.DB;
|
|
3
|
+
|
|
4
|
+
namespace RevitBridge.Tools;
|
|
5
|
+
|
|
6
|
+
internal sealed class QuerySpatialElements : ITool
|
|
7
|
+
{
|
|
8
|
+
public string Name => "query_spatial_elements";
|
|
9
|
+
public string Label => "Query Spatial Elements";
|
|
10
|
+
public string Tier => "advanced";
|
|
11
|
+
public string Description => "Find host elements whose model axis-aligned bounding boxes intersect or lie inside an explicit region. Coordinates use document internal axes and the required length unit. query uses get_elements filters, covering the whole scope (maximum 10,000 candidates). Includes touching boundaries; elements without bounds are counted separately. Bounds may include nonphysical geometry, so these are approximate candidates, not solid intersections or clashes. Linked contents are not traversed. Results are ordered by element ID with paging.";
|
|
12
|
+
public object ParametersSchema => new
|
|
13
|
+
{
|
|
14
|
+
type = "object", properties = new
|
|
15
|
+
{
|
|
16
|
+
query = ElementQueryScope.Schema(),
|
|
17
|
+
min = ModelEditInputs.VectorSchema("Region minimum in document internal axes, in unit."),
|
|
18
|
+
max = ModelEditInputs.VectorSchema("Region maximum in document internal axes, in unit."),
|
|
19
|
+
unit = ModelEditInputs.LengthUnitSchema,
|
|
20
|
+
relation = new { type = "string", @enum = new[] { "intersects", "inside" } },
|
|
21
|
+
offset = new { type = "integer", minimum = 0 }, limit = new { type = "integer", minimum = 1, maximum = 200 },
|
|
22
|
+
}, required = new[] { "min", "max", "unit" },
|
|
23
|
+
};
|
|
24
|
+
public object Execute(JsonElement args, ToolContext context)
|
|
25
|
+
{
|
|
26
|
+
var doc = context.Document ?? throw new NoActiveDocumentException();
|
|
27
|
+
double scale = ModelEditInputs.LengthScale(args);
|
|
28
|
+
var region = new SpatialBounds(ModelEditInputs.Vector(args, "min").Multiply(scale), ModelEditInputs.Vector(args, "max").Multiply(scale));
|
|
29
|
+
if (region.Min.X > region.Max.X || region.Min.Y > region.Max.Y || region.Min.Z > region.Max.Z)
|
|
30
|
+
throw new ArgumentException("Each min coordinate must be less than or equal to max.");
|
|
31
|
+
string relation = JsonArgs.GetString(args, "relation") ?? "intersects";
|
|
32
|
+
if (relation is not ("intersects" or "inside")) throw new ArgumentException("relation must be intersects or inside.");
|
|
33
|
+
var query = ElementQueryScope.Parse(args);
|
|
34
|
+
JsonElement RunQuery() => JsonSerializer.SerializeToElement(((ToolOutput)new GetElements().Execute(JsonSerializer.SerializeToElement(query), context)!).Payload);
|
|
35
|
+
query["count_only"] = true;
|
|
36
|
+
var counted = RunQuery();
|
|
37
|
+
int count = counted.GetProperty("total_count").GetInt32();
|
|
38
|
+
if (count > 10000) throw new ArgumentException($"Query has {count} candidates; narrow query to at most 10,000.");
|
|
39
|
+
object? warnings = counted.TryGetProperty("warnings", out var warning) ? warning.Clone() : null;
|
|
40
|
+
query["count_only"] = false; query["limit"] = 1000;
|
|
41
|
+
var matches = new List<(Element Element, SpatialBounds Bounds)>();
|
|
42
|
+
int withoutBounds = 0;
|
|
43
|
+
for (int start = 0; start < count; start += 1000)
|
|
44
|
+
{
|
|
45
|
+
query["offset"] = start;
|
|
46
|
+
foreach (var row in RunQuery().GetProperty("elements").EnumerateArray())
|
|
47
|
+
{
|
|
48
|
+
var element = doc.GetElement(new ElementId(row.GetProperty("id").GetInt64()));
|
|
49
|
+
var bounds = SpatialBounds.Of(element);
|
|
50
|
+
if (bounds == null) { withoutBounds++; continue; }
|
|
51
|
+
if (relation == "inside" ? region.Contains(bounds) : region.Intersects(bounds)) matches.Add((element, bounds));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
int offset = Math.Max(0, JsonArgs.GetInt(args, "offset", 0)), limit = Math.Clamp(JsonArgs.GetInt(args, "limit", 100), 1, 200);
|
|
55
|
+
var elements = matches.OrderBy(x => x.Element.Id.Value).Skip(offset).Take(limit).Select(x => new
|
|
56
|
+
{
|
|
57
|
+
id = x.Element.Id.Value, unique_id = x.Element.UniqueId, name = x.Element.Name, category = x.Element.Category?.Name, bounds = x.Bounds.Describe(scale),
|
|
58
|
+
}).ToArray();
|
|
59
|
+
return new { method = "axis_aligned_bounding_boxes", approximate = true, coordinate_system = "document_internal", unit = JsonArgs.GetString(args, "unit"),
|
|
60
|
+
relation, region = region.Describe(scale), candidate_count = count, without_bounds_count = withoutBounds, warnings,
|
|
61
|
+
total_count = matches.Count, offset, returned_count = elements.Length, next_offset = offset + elements.Length < matches.Count ? (int?)(offset + elements.Length) : null, elements };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -19,14 +19,16 @@ namespace RevitBridge.Tools
|
|
|
19
19
|
|
|
20
20
|
public string Name => "set_parameters";
|
|
21
21
|
public string Label => "Set Parameters";
|
|
22
|
-
public string Description => "Write parameter values on elements — also the home for rename: parameter 'Name' covers levels, views, sheets, types, etc. (falls back to the element's Name property when the Name parameter is read-only).
|
|
22
|
+
public string Description => "Write parameter values on elements — also the home for rename: parameter 'Name' covers levels, views, sheets, types, etc. (falls back to the element's Name property when the Name parameter is read-only). Each update has its own subtransaction inside one model transaction. Default partial success commits valid updates; atomic=true rolls everything back if any update fails. preview=true validates commit inside a transaction group, then rolls the group back. Results include observed per-step before/after values and distinguish succeeded from proposed updates. parameter accepts a display name (Comments, Mark, Name), a BuiltInParameter enum name (e.g. ALL_MODEL_MARK), or guid:<GUID> for a shared parameter; type parameters live on the element type, so pass the type's id. Values are validated against the parameter's storage type; numeric values are interpreted in the document's display units for that parameter unless 'unit' (e.g. millimeters, feet, squareMeters) is given. Revit warnings raised at commit (e.g. duplicate Mark values) are auto-dismissed and listed in commitWarnings — mention them to the user; error-severity failures roll the whole transaction back.";
|
|
23
23
|
public bool Write => true;
|
|
24
24
|
|
|
25
25
|
public object ParametersSchema => new
|
|
26
26
|
{
|
|
27
27
|
type = "object",
|
|
28
|
-
properties = new
|
|
29
|
-
{
|
|
28
|
+
properties = new
|
|
29
|
+
{
|
|
30
|
+
preview = new { type = "boolean", description = "Validate edits through Revit commit, then roll back the enclosing transaction group. Returns proposed before/after values; no model changes remain. Default false." },
|
|
31
|
+
atomic = new { type = "boolean", description = "Roll back all updates if any fails. Default false preserves successful updates. Each update is isolated in a subtransaction." },
|
|
30
32
|
updates = new
|
|
31
33
|
{
|
|
32
34
|
type = "array",
|
|
@@ -57,95 +59,40 @@ namespace RevitBridge.Tools
|
|
|
57
59
|
public IReadOnlyList<string>? PromptGuidelines => new[]
|
|
58
60
|
{
|
|
59
61
|
"Use set_parameters for parameter writes and renames (parameter 'Name'); pass 'unit' with numeric values unless the document's display units are intended.",
|
|
60
|
-
"set_parameters
|
|
62
|
+
"set_parameters defaults to partial success; use atomic=true for all-or-nothing batches and preview=true for commit-validated model rollback. Always inspect committed, proposed and failed.",
|
|
61
63
|
};
|
|
62
64
|
|
|
63
|
-
public object? Execute(JsonElement args, ToolContext context)
|
|
64
|
-
{
|
|
65
|
-
var doc = context.Document ?? throw new NoActiveDocumentException();
|
|
66
|
-
DocumentGuard.CheckExpectedDocument(args, doc);
|
|
67
|
-
var updates = ParseUpdates(args);
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
["newDisplayValue"] = newDisplayValue,
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
catch (Exception ex)
|
|
92
|
-
{
|
|
93
|
-
failed.Add(new Dictionary<string, object?>
|
|
94
|
-
{
|
|
95
|
-
["id"] = update.ElementId,
|
|
96
|
-
["parameter"] = update.ParameterInput,
|
|
97
|
-
["reason"] = ex.Message,
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (succeeded.Count > 0)
|
|
103
|
-
{
|
|
104
|
-
var status = transaction.Commit();
|
|
105
|
-
var finalStatus = transaction.GetStatus();
|
|
106
|
-
if (status != TransactionStatus.Committed || finalStatus != TransactionStatus.Committed)
|
|
107
|
-
throw new InvalidOperationException(
|
|
108
|
-
$"The set_parameters commit returned {status}; current transaction status is {finalStatus}."
|
|
109
|
-
+ failureGuard.DescribeErrors());
|
|
110
|
-
}
|
|
111
|
-
else
|
|
112
|
-
{
|
|
113
|
-
var status = transaction.RollBack();
|
|
114
|
-
if (status != TransactionStatus.RolledBack || transaction.GetStatus() != TransactionStatus.RolledBack)
|
|
115
|
-
throw new InvalidOperationException($"The set_parameters rollback returned {status}.");
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
catch (Exception ex)
|
|
119
|
-
{
|
|
120
|
-
throw new InvalidOperationException($"{ex.Message} {FailureGuard.RollBackAndDescribe(transaction)}", ex);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
int elementCount = succeeded.Select(row => row["id"]).Distinct().Count();
|
|
124
|
-
string failureSample = failed.Count > 0
|
|
125
|
-
? $" First failure: element {failed[0]["id"]} '{failed[0]["parameter"]}': {failed[0]["reason"]}"
|
|
126
|
-
: string.Empty;
|
|
127
|
-
string compact = failed.Count == 0
|
|
128
|
-
? $"Updated {succeeded.Count} parameter value(s) on {elementCount} element(s)."
|
|
129
|
-
: succeeded.Count == 0
|
|
130
|
-
? $"No updates applied; all {failed.Count} failed — transaction rolled back.{failureSample}"
|
|
131
|
-
: $"Updated {succeeded.Count} parameter value(s) on {elementCount} element(s); {failed.Count} failed.{failureSample}";
|
|
132
|
-
if (failureGuard.Warnings.Count > 0)
|
|
133
|
-
compact += $" {failureGuard.Warnings.Count} Revit warning(s) auto-dismissed at commit (see commitWarnings), e.g.: {failureGuard.Warnings[0]}";
|
|
134
|
-
|
|
135
|
-
return new ToolOutput(new
|
|
136
|
-
{
|
|
137
|
-
updated = succeeded.Count,
|
|
138
|
-
committed = succeeded.Count > 0,
|
|
139
|
-
succeeded,
|
|
140
|
-
failed,
|
|
141
|
-
commitWarnings = failureGuard.Warnings,
|
|
142
|
-
}, compact);
|
|
143
|
-
}
|
|
144
|
-
|
|
65
|
+
public object? Execute(JsonElement args, ToolContext context)
|
|
66
|
+
{
|
|
67
|
+
var doc = context.Document ?? throw new NoActiveDocumentException();
|
|
68
|
+
DocumentGuard.CheckExpectedDocument(args, doc);
|
|
69
|
+
var updates = ParseUpdates(args);
|
|
70
|
+
var steps = updates.Select(update => new ModelEditBatch.Step(
|
|
71
|
+
new Dictionary<string, object?> { ["id"] = update.ElementId, ["parameter"] = update.ParameterInput },
|
|
72
|
+
() =>
|
|
73
|
+
{
|
|
74
|
+
var change = Apply(doc, update);
|
|
75
|
+
return new Dictionary<string, object?>
|
|
76
|
+
{
|
|
77
|
+
["before"] = change.Before, ["after"] = change.After,
|
|
78
|
+
["newDisplayValue"] = change.Display,
|
|
79
|
+
};
|
|
80
|
+
})).ToArray();
|
|
81
|
+
var result = ModelEditBatch.Run(doc, Name, args, steps);
|
|
82
|
+
string summary = result.Preview
|
|
83
|
+
? $"Preview rolled back: {result.Proposed.Count} proposed update(s), {result.Failed.Count} failure(s)."
|
|
84
|
+
: result.Committed
|
|
85
|
+
? $"Applied {result.Succeeded.Count} parameter update(s); {result.Failed.Count} failed."
|
|
86
|
+
: $"No updates applied; {result.Failed.Count} failed. Batch rolled back.";
|
|
87
|
+
return new ToolOutput(result.Payload, summary);
|
|
88
|
+
}
|
|
89
|
+
|
|
145
90
|
// ------------------------------------------------------------- application
|
|
146
91
|
|
|
147
|
-
|
|
148
|
-
|
|
92
|
+
private sealed record ValueChange(string? Display, object? Before, object? After);
|
|
93
|
+
|
|
94
|
+
/// <summary>Snapshots the concrete parameter or rename target actually written by this step.</summary>
|
|
95
|
+
private static ValueChange Apply(Document doc, Update update)
|
|
149
96
|
{
|
|
150
97
|
var element = doc.GetElement(new ElementId(update.ElementId))
|
|
151
98
|
?? throw new InvalidOperationException($"Element {update.ElementId} not found.");
|
|
@@ -168,7 +115,8 @@ namespace RevitBridge.Tools
|
|
|
168
115
|
throw new InvalidOperationException($"Parameter '{parameter.Definition?.Name}' is read-only on element {update.ElementId}.");
|
|
169
116
|
}
|
|
170
117
|
|
|
171
|
-
|
|
118
|
+
var before = GetElementDetails.DescribeParameter(doc, parameter, parameter.Definition?.Name ?? "", false);
|
|
119
|
+
bool accepted = parameter.StorageType switch
|
|
172
120
|
{
|
|
173
121
|
StorageType.String => parameter.Set(ValueAsString(update.Value)),
|
|
174
122
|
StorageType.Double => parameter.Set(ToInternalUnits(doc, parameter, RequireDouble(parameter, update.Value), update.Unit)),
|
|
@@ -183,17 +131,19 @@ namespace RevitBridge.Tools
|
|
|
183
131
|
return SetElementName(element, update);
|
|
184
132
|
throw new InvalidOperationException($"Revit rejected the value for '{parameter.Definition?.Name}' on element {update.ElementId} (Parameter.Set returned false).");
|
|
185
133
|
}
|
|
186
|
-
return DescribeNewValue(parameter) ?? ValueAsString(update.Value)
|
|
134
|
+
return new ValueChange(DescribeNewValue(parameter) ?? ValueAsString(update.Value), before,
|
|
135
|
+
GetElementDetails.DescribeParameter(doc, parameter, parameter.Definition?.Name ?? "", false));
|
|
187
136
|
}
|
|
188
137
|
|
|
189
138
|
/// <summary>Rename fallback: the Element.Name property setter.</summary>
|
|
190
|
-
private static
|
|
139
|
+
private static ValueChange SetElementName(Element element, Update update)
|
|
191
140
|
{
|
|
192
141
|
string name = ValueAsString(update.Value);
|
|
193
142
|
if (string.IsNullOrWhiteSpace(name))
|
|
194
143
|
throw new InvalidOperationException("Renaming needs a non-empty string value.");
|
|
195
|
-
element.Name =
|
|
196
|
-
|
|
144
|
+
var before = new { value = element.Name, displayValue = element.Name, storageType = "String" };
|
|
145
|
+
element.Name = name;
|
|
146
|
+
return new ValueChange(name, before, new { value = element.Name, displayValue = element.Name, storageType = "String" });
|
|
197
147
|
}
|
|
198
148
|
|
|
199
149
|
private static Parameter? FindParameter(Element element, Update update)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
using Autodesk.Revit.DB;
|
|
2
|
+
|
|
3
|
+
namespace RevitBridge.Tools;
|
|
4
|
+
|
|
5
|
+
internal sealed record SpatialBounds(XYZ Min, XYZ Max)
|
|
6
|
+
{
|
|
7
|
+
public static SpatialBounds? Of(Element element)
|
|
8
|
+
{
|
|
9
|
+
var box = element.get_BoundingBox(null);
|
|
10
|
+
if (box == null) return null;
|
|
11
|
+
var corners = new List<XYZ>(8);
|
|
12
|
+
foreach (double x in new[] { box.Min.X, box.Max.X })
|
|
13
|
+
foreach (double y in new[] { box.Min.Y, box.Max.Y })
|
|
14
|
+
foreach (double z in new[] { box.Min.Z, box.Max.Z })
|
|
15
|
+
corners.Add(box.Transform.OfPoint(new XYZ(x, y, z)));
|
|
16
|
+
return new(new(corners.Min(p => p.X), corners.Min(p => p.Y), corners.Min(p => p.Z)),
|
|
17
|
+
new(corners.Max(p => p.X), corners.Max(p => p.Y), corners.Max(p => p.Z)));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public bool Intersects(SpatialBounds other) => Min.X <= other.Max.X && Max.X >= other.Min.X
|
|
21
|
+
&& Min.Y <= other.Max.Y && Max.Y >= other.Min.Y && Min.Z <= other.Max.Z && Max.Z >= other.Min.Z;
|
|
22
|
+
public bool Contains(SpatialBounds other) => Min.X <= other.Min.X && Max.X >= other.Max.X
|
|
23
|
+
&& Min.Y <= other.Min.Y && Max.Y >= other.Max.Y && Min.Z <= other.Min.Z && Max.Z >= other.Max.Z;
|
|
24
|
+
public XYZ Gap(SpatialBounds other) => new(
|
|
25
|
+
Math.Max(0, Math.Max(Min.X - other.Max.X, other.Min.X - Max.X)),
|
|
26
|
+
Math.Max(0, Math.Max(Min.Y - other.Max.Y, other.Min.Y - Max.Y)),
|
|
27
|
+
Math.Max(0, Math.Max(Min.Z - other.Max.Z, other.Min.Z - Max.Z)));
|
|
28
|
+
public static double[] Coordinates(XYZ p, double scale) => new[] { p.X / scale, p.Y / scale, p.Z / scale };
|
|
29
|
+
public object Describe(double scale) => new { min = Coordinates(Min, scale), max = Coordinates(Max, scale) };
|
|
30
|
+
}
|