Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -194,4 +194,11 @@ await _threadContext.RunOnMainAsync(async () =>
}
});
}

public async Task UpdateParameters(string payload) =>
await Commands.SetGlobalNotification(
ToastNotificationType.INFO,
"Not Supported",
"Applying parameter changes is not yet supported in this host application"
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,11 @@ public void RemoveModels(List<ModelCard> models) =>
public Task HighlightModel(string modelCardId) => Task.CompletedTask;

public Task HighlightObjects(IReadOnlyList<string> objectIds) => Task.CompletedTask;

public async Task UpdateParameters(string payload) =>
await Commands.SetGlobalNotification(
ToastNotificationType.INFO,
"Not Supported",
"Applying parameter changes is not yet supported in this host application"
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,11 @@ ISpeckleApplication speckleApplication
public async Task HighlightObjects(IReadOnlyList<string> objectIds) =>
// TODO: Implement highlighting logic on main thread
await Task.CompletedTask;

public async Task UpdateParameters(string payload) =>
await Commands.SetGlobalNotification(
ToastNotificationType.INFO,
"Not Supported",
"Applying parameter changes is not yet supported in this host application"
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Models;
using Speckle.Connectors.DUI.Models.Card;
using Speckle.Connectors.DUI.Utils;
using Speckle.Connectors.Revit.HostApp;
using Speckle.Connectors.Revit.Plugin;
using Speckle.Connectors.RevitShared;
using Speckle.Connectors.RevitShared.Operations.Send.Filters;
Expand All @@ -24,14 +26,18 @@ internal sealed class BasicConnectorBindingRevit : IBasicConnectorBinding
private readonly ISpeckleApplication _speckleApplication;
private readonly ITopLevelExceptionHandler _topLevelExceptionHandler;
private readonly IRevitTask _revitTask;
private readonly ParameterUpdater _parameterUpdater;
private readonly IJsonSerializer _jsonSerializer;

public BasicConnectorBindingRevit(
DocumentModelStore store,
IBrowserBridge parent,
RevitContext revitContext,
ISpeckleApplication speckleApplication,
ITopLevelExceptionHandler topLevelExceptionHandler,
IRevitTask revitTask
IRevitTask revitTask,
ParameterUpdater parameterUpdater,
IJsonSerializer jsonSerializer
)
{
Name = "baseBinding";
Expand All @@ -41,6 +47,8 @@ IRevitTask revitTask
_speckleApplication = speckleApplication;
_topLevelExceptionHandler = topLevelExceptionHandler;
_revitTask = revitTask;
_parameterUpdater = parameterUpdater;
_jsonSerializer = jsonSerializer;
Commands = new BasicConnectorBindingCommands(parent);

_store.DocumentChanged += (_, _) =>
Expand Down Expand Up @@ -191,4 +199,132 @@ await _revitTask
// activeUIDoc.ShowElements(objectIds);
// ;
}

public async Task UpdateParameters(string payload)
{
try
{
var wrapper = _jsonSerializer.Deserialize<ParameterChangesWrapper>(payload);
var requests = wrapper?.Changes;

if (requests == null || requests.Count == 0)
{
return;
}

var activeUIDoc =
_revitContext.UIApplication?.ActiveUIDocument
?? throw new SpeckleException("Unable to retrieve active UI document");
var doc = activeUIDoc.Document;

int successCount = 0;
List<string> errors = [];

await _revitTask
.RunAsync(() =>
{
using var t = new Transaction(doc, "Speckle: Apply Parameter Changes");
t.Start();

foreach (var request in requests)
{
if (string.IsNullOrEmpty(request.ApplicationId))
{
errors.Add("Missing ApplicationId.");
continue;
}

var elementId = ElementIdHelper.GetElementIdFromUniqueId(doc, request.ApplicationId);
if (elementId == null)
{
errors.Add($"Element UniqueId not found: {request.ApplicationId}");
continue;
}

var element = doc.GetElement(elementId);
if (element == null)
{
errors.Add($"Element is null for Id: {elementId}");
continue;
}

var rawPath = request.Path;
if (string.IsNullOrEmpty(rawPath))
{
errors.Add("Path is missing.");
continue;
}

// TODO: not happy about this
// 👇
if (rawPath.StartsWith("properties.", StringComparison.InvariantCultureIgnoreCase))
{
rawPath = rawPath[11..];
}
if (rawPath.StartsWith("parameters.", StringComparison.InvariantCultureIgnoreCase))
{
rawPath = rawPath[11..];
}

var pathParts = rawPath.Split(['.'], 3);
if (pathParts.Length != 3)
{
errors.Add($"Path must have 3 parts. Got: '{rawPath}'");
continue;
}
// ☝️
// TODO: not happy about this

object? rawValue = request.To;
if (rawValue is Newtonsoft.Json.Linq.JValue jValue)
{
rawValue = jValue.Value;
}

var result = _parameterUpdater.Update(element, pathParts, rawValue);

if (result.IsSuccess)
{
successCount++;
}
else
{
errors.Add($"[{pathParts[2]}]: {result.ErrorMessage}");
}
}

t.Commit();
})
.ConfigureAwait(false);

if (errors.Count > 0)
{
await Commands.SetGlobalNotification(
ToastNotificationType.WARNING,
"Update Completed with Issues",
$"Applied {successCount} updates. Encountered {errors.Count} errors: {string.Join(" | ", errors.Take(3))}",
autoClose: false
);
}
else
{
await Commands.SetGlobalNotification(
ToastNotificationType.SUCCESS,
"Parameters Updated",
$"Successfully applied {successCount} updates."
);
}
}
catch (Exception ex)
{
_topLevelExceptionHandler.CatchUnhandled(
() => throw new SpeckleException("Failed to apply parameter updates", ex)
);
}
}
}

public class ParameterChangesWrapper
{
public List<ParameterChangeRequest>? Changes { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
private readonly LinkedModelHandler _linkedModelHandler;
private readonly IThreadContext _threadContext;
private readonly ISendOperationManagerFactory _sendOperationManagerFactory;
private readonly ParameterUpdater _parameterUpdater;
private bool _isDocChangedSubscribed;
private EventHandler<Autodesk.Revit.DB.Events.DocumentChangedEventArgs>? _documentChangedHandler;
private readonly ConnectorConfig _config;
Expand Down Expand Up @@ -67,6 +68,7 @@ public RevitSendBinding(
IThreadContext threadContext,
IRevitTask revitTask,
ISendOperationManagerFactory sendOperationManagerFactory,
ParameterUpdater parameterUpdater,
IConfigStore configStore
)
: base("sendBinding", bridge)
Expand All @@ -84,6 +86,7 @@ IConfigStore configStore
_linkedModelHandler = linkedModelHandler;
_threadContext = threadContext;
_sendOperationManagerFactory = sendOperationManagerFactory;
_parameterUpdater = parameterUpdater;
_config = configStore.GetConnectorConfig();

Commands = new SendBindingUICommands(bridge);
Expand Down Expand Up @@ -198,6 +201,44 @@ await manager.Process<DocumentToConvert>(
);
}

public async Task UpdateParameters(List<ParameterChangeRequest> changes)
{
var document = _revitContext.UIApplication?.ActiveUIDocument?.Document;
if (document == null)
{
throw new SpeckleException("No document is active.");
}

await _threadContext.RunOnMainAsync(() =>
{
using var transaction = new Transaction(document, "Speckle Parameter Updates");
transaction.Start();

foreach (var change in changes)
{
var element = document.GetElement(change.ApplicationId);
if (element == null)
{
continue;
}

var path = ParsePath(change.Path);
var result = _parameterUpdater.Update(element, path, change.To);
}

transaction.Commit();
return Task.FromResult(true);
});
}

private string[] ParsePath(string concatenatedPath)
{
// "properties.Parameters.Type Parameters.Other.Family Name"
// → ["Type Parameters", "Other", "Family Name"]
var segments = concatenatedPath.Split('.');
return segments.Skip(2).ToArray();
}

private static (string? fileName, long? fileBytes) GetFileInfo(Document document)
{
string fullPath = document.PathName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ public static void AddRevit(this IServiceCollection serviceCollection)
serviceCollection.AddSingleton<ToSpeckleSettingsManager>();
serviceCollection.AddSingleton<ToHostSettingsManager>();
serviceCollection.AddSingleton<LinkedModelHandler>();
serviceCollection.AddSingleton<ParameterUpdater>();

// receive operation and dependencies
serviceCollection.AddScoped<IHostObjectBuilder, RevitHostObjectBuilder>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Speckle.Connectors.Revit.HostApp;

public class ParameterChangeRequest
{
public required string ApplicationId { get; init; }
public required string Path { get; init; }
public object? To { get; init; }
}
Loading