Compare commits

..

4 Commits

Author SHA1 Message Date
Jedd Morgan b485a4ff6f Don't build everything when zero changes (#715)
.NET Build and Publish / build-windows (push) Has been cancelled
.NET Build and Publish / build-linux (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
2025-03-26 15:21:12 +00:00
Jedd Morgan 5697afc292 fix(ifc): Fixed regression with IFC Site geometry not being converted (#712)
* IFC spatial elements now attach geometry as separate data object

* removed unnecessary attribute

* Updated tester for faster testing
2025-03-26 14:57:50 +00:00
Adam Hathcock 4ec45d3cd5 Merge pull request #709 from specklesystems/dev
.NET Build and Publish / build-windows (push) Has been cancelled
.NET Build and Publish / build-linux (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
Bump sdk to 3.1.7 (#708)
2025-03-25 12:14:05 +00:00
Jedd Morgan ac1345bbaf Merge pull request #703 from specklesystems/dev
Update dev into main
2025-03-25 11:54:03 +00:00
10 changed files with 78 additions and 89 deletions
+1 -4
View File
@@ -66,10 +66,7 @@ public static class Affected
Console.WriteLine("Affected project group being built: " + group.HostAppSlug);
}
if (groups.Count > 0)
{
return groups.ToArray();
}
return groups.ToArray();
}
Console.WriteLine("Using all project groups: " + string.Join(',', Consts.ProjectGroups));
@@ -3,8 +3,6 @@ using Autodesk.Revit.DB;
using Speckle.Connectors.DUI.Models.Card.SendFilter;
using Speckle.Connectors.RevitShared;
using Speckle.Connectors.RevitShared.Operations.Send.Filters;
using Speckle.Converters.RevitShared.Helpers;
using Speckle.Sdk.Common;
namespace Speckle.Connectors.Revit.HostApp;
@@ -16,14 +14,9 @@ namespace Speckle.Connectors.Revit.HostApp;
/// </summary>
public class LinkedModelHandler
{
private readonly RevitContext _revitContext;
// Dictionary to track linked model display names
public Dictionary<string, string> LinkedModelDisplayNames { get; } = new();
public LinkedModelHandler(RevitContext revitContext)
{
_revitContext = revitContext;
}
/// <summary>
/// Gets elements from a linked document based on the provided send filter.
/// This method handles the specifics of element collection but doesn't make decisions
@@ -45,36 +38,6 @@ public class LinkedModelHandler
}
return new List<Element>();
}
// send mode → Views (taken from the legacy code)
if (sendFilter is RevitViewsFilter viewFilter && viewFilter.GetView() != null)
{
RevitLinkInstance linkInstance = FindLinkInstanceForDocument(
linkedDocument.PathName,
_revitContext.UIApplication.NotNull().ActiveUIDocument.Document
);
#if REVIT2024_OR_GREATER
// revit 2024 and 2025 we can use the three-parameter constructor to get only visible elements
using var viewCollector = new FilteredElementCollector(
_revitContext.UIApplication.ActiveUIDocument.Document,
viewFilter.GetView().NotNull().Id,
linkInstance.Id
);
return viewCollector.WhereElementIsNotElementType().ToElements().ToList();
#else
// 🚨 LIMITATION: in Revit 2023 and below, we can only check if the entire linked model is visible,
// not individual elements within it. If the linked model is visible, all its elements will be included.
// constructor overload pertaining to searching and filtering visible elements from a revit link only added 2024.
if (linkInstance.IsHidden(viewFilter.GetView().NotNull()))
{
return new List<Element>(); // if the linked model is hidden, return no elements
}
// 💩 fallback to getting all elements if the linked model is visible
return GetAllElementsForLinkedModelSelection(linkedDocument);
#endif
}
// send mode → Selection
return GetAllElementsForLinkedModelSelection(linkedDocument);
}
@@ -165,14 +128,4 @@ public class LinkedModelHandler
using var collector = new FilteredElementCollector(linkedDoc);
return collector.WhereElementIsNotElementType().WhereElementIsViewIndependent().ToList();
}
private RevitLinkInstance FindLinkInstanceForDocument(string linkedDocumentPath, Document mainDocument)
{
using var collector = new FilteredElementCollector(mainDocument);
return collector
.OfClass(typeof(RevitLinkInstance))
.Cast<RevitLinkInstance>()
.FirstOrDefault(link => link.GetLinkDocument()?.PathName == linkedDocumentPath)
.NotNull();
}
}
@@ -5,6 +5,7 @@ using Speckle.Connectors.Common.Caching;
using Speckle.Connectors.Common.Conversion;
using Speckle.Connectors.Common.Extensions;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.Common.Threading;
using Speckle.Connectors.DUI.Exceptions;
using Speckle.Connectors.Revit.HostApp;
using Speckle.Converters.Common;
@@ -22,6 +23,7 @@ public class RevitRootObjectBuilder(
IConverterSettingsStore<RevitConversionSettings> converterSettings,
ISendConversionCache sendConversionCache,
ElementUnpacker elementUnpacker,
IThreadContext threadContext,
SendCollectionManager sendCollectionManager,
ILogger<RevitRootObjectBuilder> logger,
RevitToSpeckleCacheSingleton revitToSpeckleCacheSingleton,
@@ -33,6 +35,16 @@ public class RevitRootObjectBuilder(
SendInfo sendInfo,
IProgress<CardProgress> onOperationProgressed,
CancellationToken ct = default
) =>
threadContext.RunOnMainAsync(
() => Task.FromResult(BuildSync(documentElementContexts, sendInfo, onOperationProgressed, ct))
);
private RootObjectBuilderResult BuildSync(
IReadOnlyList<DocumentToConvert> documentElementContexts,
SendInfo sendInfo,
IProgress<CardProgress> onOperationProgressed,
CancellationToken cancellationToken
)
{
var doc = converterSettings.Current.Document;
@@ -143,7 +155,7 @@ public class RevitRootObjectBuilder(
var atomicObjects = atomicObjectByDocumentAndTransform.Elements;
foreach (Element revitElement in atomicObjects)
{
ct.ThrowIfCancellationRequested();
cancellationToken.ThrowIfCancellationRequested();
string applicationId = revitElement.UniqueId;
string sourceType = revitElement.GetType().Name;
try
@@ -229,6 +241,6 @@ public class RevitRootObjectBuilder(
// NOTE: these are currently not used anywhere, we'll skip them until someone calls for it back
// rootObject[ProxyKeys.PARAMETER_DEFINITIONS] = _parameterDefinitionHandler.Definitions;
return Task.FromResult(new RootObjectBuilderResult(rootObject, results));
return new RootObjectBuilderResult(rootObject, results);
}
}
@@ -128,7 +128,7 @@ public abstract class DocumentModelStore(IJsonSerializer serializer)
}
}
protected string Serialize() => serializer.Serialize(Models.ToList());
protected string Serialize() => serializer.Serialize(Models);
// POC: this seemms more like a IModelsDeserializer?, seems disconnected from this class
protected List<ModelCard> Deserialize(string models) => serializer.Deserialize<List<ModelCard>>(models).NotNull();
@@ -15,18 +15,32 @@ namespace Speckle.Importers.Ifc.Tester2;
public sealed class IfcTester(IClientFactory clientFactory, Importer importer, IAccountManager accountManager)
{
// Settings, Change these to suit!
private readonly FilePath _filePath =
//new(@"C:\Users\Jedd\Desktop\GRAPHISOFT_Archicad_Sample_Project-S-Office_v1.0_AC25.ifc");
new(@"C:\Users\Jedd\Desktop\EST-BRE-AF-3D-BT1-30-SD-00001-A-P.ifc");
// private readonly ICollection<FilePath> _filePath = [new(@"C:\Users\Jedd\Desktop\GRAPHISOFT_Archicad_Sample_Project-S-Office_v1.0_AC25.ifc")]
private readonly IEnumerable<string> _filePaths = Directory.EnumerateFiles(@"C:\Users\Jedd\Desktop\", "*.ifc");
private readonly Uri _serverUrl = new("https://app.speckle.systems");
private const string PROJECT_ID = "f3a42bdf24";
public async Task Run()
public async Task Run(CancellationToken cancellationToken = default)
{
var account = accountManager.GetAccounts(_serverUrl).First();
using var speckleClient = clientFactory.Create(account);
string modelName = _filePath.GetFileName();
var existing = await speckleClient.Project.GetWithModels(PROJECT_ID, 1, modelsFilter: new(search: modelName));
foreach (var path in _filePaths)
{
await ImportFile(speckleClient, path, cancellationToken);
}
}
private async Task ImportFile(Client speckleClient, FilePath filePath, CancellationToken cancellationToken)
{
string modelName = filePath.GetFileName();
var existing = await speckleClient.Project.GetWithModels(
PROJECT_ID,
1,
modelsFilter: new(search: modelName),
cancellationToken: cancellationToken
);
string? existingModel = existing.models.items.Count >= 1 ? existing.models.items.First().id : null;
// Convert IFC to Speckle Objects
@@ -35,14 +49,14 @@ public sealed class IfcTester(IClientFactory clientFactory, Importer importer, I
new()
{
ServerUrl = _serverUrl,
FilePath = _filePath.ToString(),
FilePath = filePath.ToString(),
ProjectId = PROJECT_ID,
ModelId = existingModel,
ModelName = _filePath.GetFileName(),
ModelName = filePath.GetFileName(),
VersionMessage = "",
Token = account.token
Token = speckleClient.Account.token
};
var version = await importer.ImportIfc(args, null, default);
var version = await importer.ImportIfc(args, null, cancellationToken);
Console.WriteLine($"File was successfully sent {version.id}");
}
}
@@ -1,5 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using Speckle.Importers.Ifc.Ara3D.IfcParser.Schema;
using Speckle.Importers.Ifc.Ara3D.IfcParser.Schema;
using Speckle.Importers.Ifc.Ara3D.StepParser;
namespace Speckle.Importers.Ifc.Ara3D.IfcParser;
@@ -34,20 +33,19 @@ public class IfcEntity
public override string ToString() => $"{Type}#{Id}";
[MemberNotNullWhen(true, nameof(Guid))]
public bool IsIfcRoot => Count >= 4 && this[0] is StepString && (this[1] is StepId) || (this[1] is StepUnassigned);
// Modern IFC files conform to this, but older ones have been observed to have different length IDs.
// Leaving as a comment for now.
//&& str.Value.Length == 22;
public string? Guid => IsIfcRoot ? ((StepString)this[0]).Value.ToString() : null;
public string Guid => ((StepString)this[0]).Value.ToString();
public uint OwnerId => IsIfcRoot ? (this[1] as StepId)?.Id ?? 0 : 0;
public uint OwnerId => (this[1] as StepId)?.Id ?? 0;
public string? Name => IsIfcRoot ? (this[2] as StepString)?.AsString() : null;
public string? Name => (this[2] as StepString)?.AsString();
public string? Description => IsIfcRoot ? (this[3] as StepString)?.AsString() : null;
public string? Description => (this[3] as StepString)?.AsString();
public int Count => LineData.Count;
@@ -11,9 +11,6 @@ public sealed class DataObjectConverter(IGeometryConverter geometryConverter) :
{
public DataObject Convert(IfcModel model, IfcNode node, INodeConverter childrenConverter)
{
if (!node.IsIfcRoot)
throw new ArgumentException("Expected to be an IfcRoot", paramName: nameof(node));
// Even if there is no geometry, this will return an empty collection.
var geo = model.GetGeometry(node.Id);
List<Base> displayValue = geo != null ? geometryConverter.Convert(geo) : new();
@@ -24,7 +21,7 @@ public sealed class DataObjectConverter(IGeometryConverter geometryConverter) :
properties = node.ConvertPropertySets(),
name = node.Name ?? node.Guid,
displayValue = displayValue,
["@elements"] = childrenConverter.ConvertChildren(model, node),
["@elements"] = childrenConverter.ConvertChildren(model, node).ToList(),
["ifcType"] = node.Type,
["expressID"] = node.Id,
["ownerId"] = node.OwnerId,
@@ -37,8 +37,8 @@ public sealed class NodeConverter(
};
}
public List<Base> ConvertChildren(IfcModel model, IfcNode node)
public IEnumerable<Base> ConvertChildren(IfcModel model, IfcNode node)
{
return node.GetChildren().Where(x => x.IsIfcRoot).Select(x => Convert(model, x)).ToList();
return node.GetChildren().Where(x => x.IsIfcRoot).Select(x => Convert(model, x));
}
}
@@ -10,14 +10,11 @@ public sealed class ProjectConverter : IProjectConverter
{
public Collection Convert(IfcModel model, IfcProject node, INodeConverter childrenConverter)
{
if (!node.IsIfcRoot) //I'd really rather have a class for this (IfcRoot : IfcNode)
throw new ArgumentException("Expected to be an IfcRoot", paramName: nameof(node));
return new Collection
{
name = node.Name ?? node.Guid,
applicationId = node.Guid,
elements = childrenConverter.ConvertChildren(model, node),
elements = childrenConverter.ConvertChildren(model, node).ToList(),
["expressID"] = node.Id,
["ownerId"] = node.OwnerId,
["ifcType"] = node.Type,
@@ -1,23 +1,41 @@
using Speckle.Importers.Ifc.Ara3D.IfcParser.Schema;
using Speckle.Importers.Ifc.Types;
using Speckle.InterfaceGenerator;
using Speckle.Objects.Data;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Collections;
namespace Speckle.Importers.Ifc.Converters;
[GenerateAutoInterface]
public sealed class IfcSpatialStructureElementConverter : IIfcSpatialStructureElementConverter
public sealed class IfcSpatialStructureElementConverter(IGeometryConverter geometryConverter)
: IIfcSpatialStructureElementConverter
{
public Collection Convert(IfcModel model, IfcSpatialStructureElement node, INodeConverter childrenConverter)
{
if (!node.IsIfcRoot) //I'd really rather have a class for this (IfcRoot : IfcNode)
throw new ArgumentException("Expected to be an IfcRoot", paramName: nameof(node));
var directGeometry = ConvertAsDataObject(model, node);
var relationalChildren = childrenConverter.ConvertChildren(model, node);
var allChildren = relationalChildren.Prepend(directGeometry).ToList();
//We're preferring to keep IFC collections lightweight, and adding a DataObject with the properties
// 1. Spatial elements can can have direct geometry (mostly only common with IFC Site)
// 2. Keeps property access simpler
return new Collection
{
name = node.Name ?? node.Guid,
applicationId = node.Guid,
elements = childrenConverter.ConvertChildren(model, node),
name = node.Name ?? node.LongName ?? node.Guid,
elements = allChildren,
["expressID"] = node.Id,
};
}
private DataObject ConvertAsDataObject(IfcModel model, IfcSpatialStructureElement node)
{
var geo = model.GetGeometry(node.Id);
List<Base> displayValue = geo != null ? geometryConverter.Convert(geo) : new();
return new DataObject
{
["expressID"] = node.Id,
["ownerId"] = node.OwnerId,
["ifcType"] = node.Type,
@@ -25,7 +43,10 @@ public sealed class IfcSpatialStructureElementConverter : IIfcSpatialStructureEl
["objectType"] = node.ObjectType,
["compositionType"] = node.CompositionType,
["longName"] = node.LongName,
["properties"] = node.ConvertPropertySets(),
name = node.Name ?? node.LongName ?? node.Guid,
applicationId = node.Guid,
properties = node.ConvertPropertySets(),
displayValue = displayValue,
};
}
}