7860c44f4e
* Dim/quack lets go (#1275) * Add model ingestion to sharp connectors * correct ingestion message * Progress * grasshopper * GH exception messages * fix GH * file names * revit file name * grasshopper file names * etabs file names * delete tests * tekla maybe * ingestion scope * bad boolean logic * Longer TimeSpan * wip upload pipe * 10s * passthrough ingestion id * happy hack time: prevent ingestion completion this is handled server-side in the processing logic. * add packfile send endpoint detection and routing Route to SendViaPackfile when the server supports the upload-signing endpoint (POST probe, 404 = unsupported) and a continuous traversal builder is registered. * Adds Continuous Traversal Builder Introduces a Continuous Traversal Builder to manage the conversion and processing of Revit elements within a Send Pipeline. --------- Co-authored-by: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> * feat(api): DI Refactor for Duck DB + Gergo's API endpoint changes (#1282) * Di * undo accidental change * Feat (duck): dui ingestion updates post upload (#1295) * Pass optional ingestion id to DUI * Make ingestion id null for the SendViaIngestion, see the note :) * feat!: Duckdev progress reporitng (#1296) * Di * throwaway from laptop * Progress reporting * Use matching logger * Revit and revert rhino unpacker progress * more revertion * make pr even cleaner * and this one * fix build issues with other connectors * SDK nuget (#1299) * Bump to 3.14.0-alpha.2 * Feat(duck): grasshopper (#1297) * Duck x Grasshopper - who would win? * Fix registration for new builder * missing imports * return version id grasshopper * Align sync resource to sync --------- Co-authored-by: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> * Bump SDK * feat(importer): rhino file importer changes for packfile (#1301) * rhino importer changes * correct deps * Bump SDK * Fix build issues * ditto * Fix build issue * Lower standards * Fix build * feat: duck for acad, civil, navis, tekla, etabs (#1300) * duck: acad, civil, etabs, tekla, navis and bump channels to 10.0.0 * notes * fix conflicts * more conflicts * Ready for testing * fix(duck): Fix send caching (#1302) * potential fix * undo-rhino chnages * fix xml comment * amended comment * revit * Fix build * Aligned converting message * fix: reoccurring object references * Bump sdk and resolve merge conflict issues * Merge pull request #1317 from specklesystems/jrm/importer-tracing feat(otel): Tracing and OTEL changes for Rhino importer * Fix revit linked model progress (#1312) * Revert otel packages * bump SDK * Trace unpacking groups * Align trace context nullability with app * Disable send caching in Navisworks * comments * Update FileimportPayload.cs * fix using directive --------- Co-authored-by: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> * Fix merge conflicts --------- Co-authored-by: Dimitrie Stefanescu <didimitrie@gmail.com> Co-authored-by: Oğuzhan Koral <45078678+oguzhankoral@users.noreply.github.com> Co-authored-by: Björn Steinhagen <88777268+bjoernsteinhagen@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Sebastian Witt <sebastian.witt@rwth-aachen.de>
399 lines
16 KiB
C#
399 lines
16 KiB
C#
using Rhino;
|
|
using Rhino.DocObjects;
|
|
using Rhino.Geometry;
|
|
using Rhino.Render;
|
|
using Speckle.Connectors.Common.Builders;
|
|
using Speckle.Connectors.Common.Conversion;
|
|
using Speckle.Connectors.Common.Extensions;
|
|
using Speckle.Connectors.Common.Operations;
|
|
using Speckle.Connectors.Common.Operations.Receive;
|
|
using Speckle.Connectors.Common.Threading;
|
|
using Speckle.Connectors.Rhino.Extensions;
|
|
using Speckle.Connectors.Rhino.HostApp;
|
|
using Speckle.Converters.Common;
|
|
using Speckle.Converters.Common.ToHost;
|
|
using Speckle.Converters.Rhino;
|
|
using Speckle.Sdk.Common;
|
|
using Speckle.Sdk.Common.Exceptions;
|
|
using Speckle.Sdk.Logging;
|
|
using Speckle.Sdk.Models;
|
|
using Speckle.Sdk.Models.Collections;
|
|
using Speckle.Sdk.Models.Instances;
|
|
using Speckle.Sdk.Pipelines.Progress;
|
|
|
|
namespace Speckle.Connectors.Rhino.Operations.Receive;
|
|
|
|
/// <summary>
|
|
/// <para>Expects to be a scoped dependency per receive operation.</para>
|
|
/// </summary>
|
|
public class RhinoHostObjectBuilder : IHostObjectBuilder
|
|
{
|
|
private readonly IRootToHostConverter _converter;
|
|
private readonly IConverterSettingsStore<RhinoConversionSettings> _converterSettings;
|
|
private readonly RhinoInstanceBaker _instanceBaker;
|
|
private readonly RhinoLayerBaker _layerBaker;
|
|
private readonly RhinoMaterialBaker _materialBaker;
|
|
private readonly RhinoColorBaker _colorBaker;
|
|
private readonly RhinoGroupBaker _groupBaker;
|
|
private readonly RhinoViewBaker _viewBaker;
|
|
private readonly RootObjectUnpacker _rootObjectUnpacker;
|
|
private readonly ISdkActivityFactory _activityFactory;
|
|
private readonly IThreadContext _threadContext;
|
|
private readonly IReceiveConversionHandler _conversionHandler;
|
|
private readonly IDataObjectInstanceRegistry _dataObjectInstanceRegistry;
|
|
private readonly DataObjectInstanceGrouper _dataObjectInstanceGrouper;
|
|
|
|
public RhinoHostObjectBuilder(
|
|
IRootToHostConverter converter,
|
|
IConverterSettingsStore<RhinoConversionSettings> converterSettings,
|
|
RhinoLayerBaker layerBaker,
|
|
RootObjectUnpacker rootObjectUnpacker,
|
|
RhinoInstanceBaker instanceBaker,
|
|
RhinoMaterialBaker materialBaker,
|
|
RhinoColorBaker colorBaker,
|
|
RhinoGroupBaker groupBaker,
|
|
RhinoViewBaker viewBaker,
|
|
ISdkActivityFactory activityFactory,
|
|
IThreadContext threadContext,
|
|
IReceiveConversionHandler conversionHandler,
|
|
IDataObjectInstanceRegistry dataObjectInstanceRegistry,
|
|
DataObjectInstanceGrouper dataObjectInstanceGrouper
|
|
)
|
|
{
|
|
_converter = converter;
|
|
_converterSettings = converterSettings;
|
|
_rootObjectUnpacker = rootObjectUnpacker;
|
|
_instanceBaker = instanceBaker;
|
|
_materialBaker = materialBaker;
|
|
_colorBaker = colorBaker;
|
|
_layerBaker = layerBaker;
|
|
_groupBaker = groupBaker;
|
|
_viewBaker = viewBaker;
|
|
_activityFactory = activityFactory;
|
|
_threadContext = threadContext;
|
|
_conversionHandler = conversionHandler;
|
|
_dataObjectInstanceRegistry = dataObjectInstanceRegistry;
|
|
_dataObjectInstanceGrouper = dataObjectInstanceGrouper;
|
|
}
|
|
|
|
#pragma warning disable CA1506
|
|
public Task<HostObjectBuilderResult> Build(
|
|
#pragma warning restore CA1506
|
|
Base rootObject,
|
|
string projectName,
|
|
string modelName,
|
|
IProgress<CardProgress> onOperationProgressed,
|
|
CancellationToken cancellationToken
|
|
)
|
|
{
|
|
using var activity = _activityFactory.Start("Build");
|
|
// POC: This is where the top level base-layer name is set. Could be abstracted or injected in the context?
|
|
var baseLayerName = $"Project {projectName}: Model {modelName}";
|
|
|
|
// 0 - Clean then Rock n Roll!
|
|
PreReceiveDeepClean(baseLayerName);
|
|
|
|
// 1 - Unpack objects and proxies from root commit object
|
|
var unpackedRoot = _rootObjectUnpacker.Unpack(rootObject);
|
|
|
|
// 2 - Split atomic objects and instance components with their path
|
|
var (atomicObjectsWithoutInstanceComponentsForConverter, instanceComponents) =
|
|
_rootObjectUnpacker.SplitAtomicObjectsAndInstances(unpackedRoot.ObjectsToConvert);
|
|
|
|
var atomicObjectsWithoutInstanceComponentsWithPath = _layerBaker.GetAtomicObjectsWithPath(
|
|
atomicObjectsWithoutInstanceComponentsForConverter
|
|
);
|
|
var instanceComponentsWithPath = _layerBaker.GetInstanceComponentsWithPath(instanceComponents);
|
|
|
|
// 2.1 - these are not captured by traversal, so we need to re-add them here
|
|
if (unpackedRoot.DefinitionProxies != null && unpackedRoot.DefinitionProxies.Count > 0)
|
|
{
|
|
var transformed = unpackedRoot.DefinitionProxies.Select(proxy =>
|
|
(Array.Empty<Collection>(), proxy as IInstanceComponent)
|
|
);
|
|
instanceComponentsWithPath.AddRange(transformed);
|
|
}
|
|
|
|
// 3 - Bake materials and colors, as they are used later down the line by layers and objects
|
|
onOperationProgressed.Report(new("Converting materials and colors", null));
|
|
if (unpackedRoot.RenderMaterialProxies != null)
|
|
{
|
|
using var _ = _activityFactory.Start("Render Materials");
|
|
_threadContext.RunOnMain(() =>
|
|
{
|
|
_materialBaker.BakeMaterials(unpackedRoot.RenderMaterialProxies);
|
|
});
|
|
}
|
|
|
|
if (unpackedRoot.ColorProxies != null)
|
|
{
|
|
_colorBaker.ParseColors(unpackedRoot.ColorProxies);
|
|
}
|
|
|
|
// 3.1 - Bake views (Named Views)
|
|
if (unpackedRoot.Cameras is not null)
|
|
{
|
|
_viewBaker.BakeViews(unpackedRoot.Cameras);
|
|
}
|
|
|
|
// 4 - Bake layers
|
|
// See [CNX-325: Rhino: Change receive operation order to increase performance](https://linear.app/speckle/issue/CNX-325/rhino-change-receive-operation-order-to-increase-performance)
|
|
onOperationProgressed.Report(new("Baking layers (redraw disabled)", null));
|
|
using (var _ = _activityFactory.Start("Pre baking layers"))
|
|
{
|
|
//Rhino 8 doesn't play nice with Eto and layers
|
|
_threadContext
|
|
.RunOnMain(() =>
|
|
{
|
|
using var layerNoDraw = new DisableRedrawScope(_converterSettings.Current.Document.Views);
|
|
var paths = atomicObjectsWithoutInstanceComponentsWithPath.Select(t => t.path).ToList();
|
|
paths.AddRange(instanceComponentsWithPath.Select(t => t.path));
|
|
_layerBaker.CreateAllLayersForReceive(paths, baseLayerName);
|
|
})
|
|
.Wait(cancellationToken);
|
|
}
|
|
|
|
// 5 - Convert atomic objects
|
|
var bakedObjectIds = new HashSet<string>();
|
|
Dictionary<string, IReadOnlyCollection<string>> applicationIdMap = new(); // This map is used in converting blocks in stage 2. keeps track of original app id => resulting new app ids post baking
|
|
HashSet<ReceiveConversionResult> conversionResults = new();
|
|
|
|
int count = 0;
|
|
using (var _ = _activityFactory.Start("Converting objects"))
|
|
{
|
|
foreach (var (path, obj) in atomicObjectsWithoutInstanceComponentsWithPath)
|
|
{
|
|
onOperationProgressed.Report(
|
|
new("Converting objects", (double)++count / atomicObjectsWithoutInstanceComponentsForConverter.Count)
|
|
);
|
|
var ex = _conversionHandler.TryConvert(() =>
|
|
{
|
|
// 0: get pre-created layer from cache in layer baker
|
|
int layerIndex = _layerBaker.GetLayerIndex(path, baseLayerName);
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
// 1: create object attributes for baking
|
|
ObjectAttributes atts = obj.GetAttributes();
|
|
atts.LayerIndex = layerIndex;
|
|
|
|
// 2: convert
|
|
var result = _converter.Convert(obj);
|
|
|
|
// 3: bake
|
|
var conversionIds = new List<string>();
|
|
if (result is GeometryBase geometryBase)
|
|
{
|
|
var guid = BakeObject(geometryBase, obj, null, atts);
|
|
conversionIds.Add(guid.ToString());
|
|
}
|
|
else if (result is List<GeometryBase> geometryBases) // one to many raw encoding case
|
|
{
|
|
// NOTE: I'm unhappy about this case (dim). It's needed as the raw encoder approach can hypothetically return
|
|
// multiple "geometry bases" - but this is not a fallback conversion.
|
|
// EXTRA NOTE: Oguzhan says i shouldn't be unhappy about this - it's a legitimate case
|
|
// EXTRA EXTRA NOTE: TY Ogu, i am no longer than unhappy about it. It's legit "mess".
|
|
foreach (var gb in geometryBases)
|
|
{
|
|
var guid = BakeObject(gb, obj, null, atts);
|
|
conversionIds.Add(guid.ToString());
|
|
}
|
|
}
|
|
else if (result is List<(GeometryBase, Base)> fallbackConversionResult) // one to many fallback conversion
|
|
{
|
|
var guids = BakeObjectsAsFallbackGroup(fallbackConversionResult, obj, atts, baseLayerName);
|
|
conversionIds.AddRange(guids.Select(id => id.ToString()));
|
|
}
|
|
|
|
if (conversionIds.Count == 0)
|
|
{
|
|
// Don't throw if this DataObject was registered for instance baking
|
|
|
|
if (!_dataObjectInstanceRegistry.IsRegistered(obj.applicationId ?? obj.id.NotNull()))
|
|
{
|
|
throw new ConversionException("Object did not convert to any native geometry");
|
|
}
|
|
// Skip normal processing - will be handled by DataObjectInstanceGrouper
|
|
return;
|
|
}
|
|
|
|
// 4: log
|
|
var id = conversionIds[0]; // this is group id if it is a one to many conversion, otherwise id of object itself
|
|
conversionResults.Add(new(Status.SUCCESS, obj, id, result.GetType().ToString()));
|
|
if (conversionIds.Count == 1)
|
|
{
|
|
bakedObjectIds.Add(id);
|
|
}
|
|
else
|
|
{
|
|
// first item always a group id if it is a one-to-many,
|
|
// we do not want to deal with later groups and its sub elements. It causes a huge issue on performance.
|
|
bakedObjectIds.AddRange(conversionIds.Skip(1));
|
|
}
|
|
|
|
// 5: populate app id map
|
|
applicationIdMap[obj.applicationId ?? obj.id.NotNull()] = conversionIds;
|
|
});
|
|
if (ex is not null)
|
|
{
|
|
conversionResults.Add(new(Status.ERROR, obj, null, null, ex));
|
|
}
|
|
}
|
|
}
|
|
|
|
// 6 - Convert instances
|
|
using (var _ = _activityFactory.Start("Converting instances"))
|
|
{
|
|
var (createdInstanceIds, consumedObjectIds, instanceConversionResults) = _instanceBaker.BakeInstances(
|
|
instanceComponentsWithPath,
|
|
applicationIdMap,
|
|
baseLayerName,
|
|
onOperationProgressed
|
|
);
|
|
|
|
bakedObjectIds.RemoveWhere(id => consumedObjectIds.Contains(id)); // remove all objects that have been "consumed"
|
|
bakedObjectIds.UnionWith(createdInstanceIds); // add instance ids
|
|
conversionResults.RemoveWhere(result => result.ResultId != null && consumedObjectIds.Contains(result.ResultId)); // remove all conversion results for atomic objects that have been consumed (POC: not that cool, but prevents problems on object highlighting)
|
|
conversionResults.UnionWith(instanceConversionResults); // add instance conversion results to our list
|
|
}
|
|
|
|
// 7.1 Group DataObject instances and apply metadata
|
|
_dataObjectInstanceGrouper.GroupAndApplyProperties();
|
|
|
|
// 7.2 Normal group creation
|
|
if (unpackedRoot.GroupProxies is not null)
|
|
{
|
|
_groupBaker.BakeGroups(unpackedRoot.GroupProxies, applicationIdMap, baseLayerName);
|
|
}
|
|
|
|
_converterSettings.Current.Document.Views.Redraw();
|
|
return Task.FromResult(new HostObjectBuilderResult(bakedObjectIds, conversionResults));
|
|
}
|
|
|
|
private void PreReceiveDeepClean(string baseLayerName)
|
|
{
|
|
// Clear DataObject instance registry at start of new build
|
|
_dataObjectInstanceRegistry.Clear();
|
|
|
|
// Remove all previously received layers and render materials from the document
|
|
int rootLayerIndex = _converterSettings.Current.Document.Layers.Find(
|
|
Guid.Empty,
|
|
baseLayerName,
|
|
RhinoMath.UnsetIntIndex
|
|
);
|
|
|
|
//Rhino 8 doesn't play nice with Eto and layers
|
|
_threadContext
|
|
.RunOnMain(() =>
|
|
{
|
|
_instanceBaker.PurgeInstances(baseLayerName);
|
|
// Materials are now reused across receives instead of being purged
|
|
// _materialBaker.PurgeMaterials(baseLayerName);
|
|
|
|
var doc = _converterSettings.Current.Document;
|
|
// Cleans up any previously received objects
|
|
if (rootLayerIndex != RhinoMath.UnsetIntIndex)
|
|
{
|
|
var documentLayer = doc.Layers[rootLayerIndex];
|
|
var childLayers = documentLayer.GetChildren();
|
|
if (childLayers != null)
|
|
{
|
|
using var layerNoDraw = new DisableRedrawScope(doc.Views);
|
|
foreach (var layer in childLayers)
|
|
{
|
|
var purgeSuccess = doc.Layers.Purge(layer.Index, true);
|
|
if (!purgeSuccess)
|
|
{
|
|
Console.WriteLine($"Failed to purge layer: {layer}");
|
|
}
|
|
}
|
|
}
|
|
doc.Layers.Purge(documentLayer.Index, true);
|
|
}
|
|
|
|
// Cleans up any previously received group
|
|
_groupBaker.PurgeGroups(baseLayerName);
|
|
})
|
|
.Wait();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bakes an object to the document.
|
|
/// </summary>
|
|
/// <param name="obj"></param>
|
|
/// <param name="originalObject"></param>
|
|
/// <param name="parentObjectId">Parent object ID for color and material proxies search (if fallback conversion was used)</param>
|
|
/// <param name="atts"></param>
|
|
/// <returns></returns>
|
|
/// <remarks>
|
|
/// Material and Color attributes are processed here due to those properties existing sometimes on fallback geometry (instead of parent).
|
|
/// and this method is called by <see cref="BakeObjectsAsFallbackGroup"/>
|
|
/// </remarks>
|
|
private Guid BakeObject(GeometryBase obj, Base originalObject, string? parentObjectId, ObjectAttributes atts)
|
|
{
|
|
var objectId = originalObject.applicationId ?? originalObject.id.NotNull();
|
|
|
|
if (_materialBaker.ObjectIdAndMaterialIdMap.TryGetValue(objectId, out Guid materialGuid))
|
|
{
|
|
atts.RenderMaterial = RenderContent.FromId(_converterSettings.Current.Document, materialGuid) as RenderMaterial;
|
|
atts.MaterialSource = ObjectMaterialSource.MaterialFromObject;
|
|
}
|
|
else if (
|
|
parentObjectId is not null
|
|
&& (_materialBaker.ObjectIdAndMaterialIdMap.TryGetValue(parentObjectId, out Guid parentGuid))
|
|
)
|
|
{
|
|
atts.RenderMaterial = RenderContent.FromId(_converterSettings.Current.Document, parentGuid) as RenderMaterial;
|
|
atts.MaterialSource = ObjectMaterialSource.MaterialFromObject;
|
|
}
|
|
|
|
if (_colorBaker.ObjectColorsIdMap.TryGetValue(objectId, out (Color, ObjectColorSource) color))
|
|
{
|
|
atts.ObjectColor = color.Item1;
|
|
atts.ColorSource = color.Item2;
|
|
}
|
|
else if (
|
|
parentObjectId is not null
|
|
&& (_colorBaker.ObjectColorsIdMap.TryGetValue(parentObjectId, out (Color, ObjectColorSource) colorSpeckleObj))
|
|
)
|
|
{
|
|
atts.ObjectColor = colorSpeckleObj.Item1;
|
|
atts.ColorSource = colorSpeckleObj.Item2;
|
|
}
|
|
|
|
return _converterSettings.Current.Document.Objects.Add(obj, atts);
|
|
}
|
|
|
|
private List<Guid> BakeObjectsAsFallbackGroup(
|
|
IEnumerable<(GeometryBase, Base)> fallbackConversionResult,
|
|
Base originatingObject,
|
|
ObjectAttributes atts,
|
|
string baseLayerName
|
|
)
|
|
{
|
|
List<Guid> objectIds = new();
|
|
string parentId = originatingObject.applicationId ?? originatingObject.id.NotNull();
|
|
int objCount = 0;
|
|
foreach (var (conversionResult, originalBaseObject) in fallbackConversionResult)
|
|
{
|
|
var id = BakeObject(conversionResult, originalBaseObject, parentId, atts);
|
|
objectIds.Add(id);
|
|
objCount++;
|
|
}
|
|
|
|
// only create groups if we really need to, ie if the fallback conversion result count is bigger than one.
|
|
if (objCount > 1)
|
|
{
|
|
var groupIndex = _converterSettings.Current.Document.Groups.Add(
|
|
$"{originatingObject.speckle_type.Split('.').Last()} - {parentId} ({baseLayerName})",
|
|
objectIds
|
|
);
|
|
|
|
var group = _converterSettings.Current.Document.Groups.FindIndex(groupIndex);
|
|
|
|
objectIds.Insert(0, group.Id);
|
|
}
|
|
|
|
return objectIds;
|
|
}
|
|
}
|