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>
298 lines
11 KiB
C#
298 lines
11 KiB
C#
using Autodesk.AutoCAD.DatabaseServices;
|
|
using Speckle.Connectors.Autocad.HostApp;
|
|
using Speckle.Connectors.Autocad.HostApp.Extensions;
|
|
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.Converters.Common;
|
|
using Speckle.Sdk.Common;
|
|
using Speckle.Sdk.Dependencies;
|
|
using Speckle.Sdk.Models;
|
|
using Speckle.Sdk.Models.Collections;
|
|
using Speckle.Sdk.Models.Instances;
|
|
using Speckle.Sdk.Pipelines.Progress;
|
|
using AutocadColor = Autodesk.AutoCAD.Colors.Color;
|
|
|
|
namespace Speckle.Connectors.Autocad.Operations.Receive;
|
|
|
|
/// <summary>
|
|
/// <para>Base class for AutoCAD host object builders. Expects to be a scoped dependency per receive operation.</para>
|
|
/// </summary>
|
|
public abstract class AutocadHostObjectBaseBuilder : IHostObjectBuilder
|
|
{
|
|
private readonly IRootToHostConverter _converter;
|
|
private readonly AutocadLayerBaker _layerBaker;
|
|
private readonly AutocadGroupBaker _groupBaker;
|
|
private readonly AutocadInstanceBaker _instanceBaker;
|
|
private readonly IAutocadMaterialBaker _materialBaker;
|
|
private readonly IAutocadColorBaker _colorBaker;
|
|
private readonly AutocadContext _autocadContext;
|
|
private readonly RootObjectUnpacker _rootObjectUnpacker;
|
|
private readonly IReceiveConversionHandler _conversionHandler;
|
|
|
|
protected AutocadHostObjectBaseBuilder(
|
|
IRootToHostConverter converter,
|
|
AutocadLayerBaker layerBaker,
|
|
AutocadGroupBaker groupBaker,
|
|
AutocadInstanceBaker instanceBaker,
|
|
IAutocadMaterialBaker materialBaker,
|
|
IAutocadColorBaker colorBaker,
|
|
AutocadContext autocadContext,
|
|
RootObjectUnpacker rootObjectUnpacker,
|
|
IReceiveConversionHandler conversionHandler
|
|
)
|
|
{
|
|
_converter = converter;
|
|
_layerBaker = layerBaker;
|
|
_groupBaker = groupBaker;
|
|
_instanceBaker = instanceBaker;
|
|
_materialBaker = materialBaker;
|
|
_colorBaker = colorBaker;
|
|
_autocadContext = autocadContext;
|
|
_rootObjectUnpacker = rootObjectUnpacker;
|
|
_conversionHandler = conversionHandler;
|
|
}
|
|
|
|
public Task<HostObjectBuilderResult> Build(
|
|
Base rootObject,
|
|
string projectName,
|
|
string modelName,
|
|
IProgress<CardProgress> onOperationProgressed,
|
|
CancellationToken cancellationToken
|
|
)
|
|
{
|
|
// Prompt the UI conversion started. Progress bar will swoosh.
|
|
onOperationProgressed.Report(new("Converting", null));
|
|
|
|
// Layer filter for received commit with project and model name
|
|
_layerBaker.CreateLayerFilter(projectName, modelName);
|
|
|
|
// 0 - Clean then Rock n Roll!
|
|
string baseLayerPrefix = _autocadContext.RemoveInvalidChars($"SPK-{projectName}-{modelName}-");
|
|
PreReceiveDeepClean(baseLayerPrefix);
|
|
|
|
// 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 (atomicObjects, instanceComponents) = _rootObjectUnpacker.SplitAtomicObjectsAndInstances(
|
|
unpackedRoot.ObjectsToConvert
|
|
);
|
|
var atomicObjectsWithPath = _layerBaker.GetAtomicObjectsWithPath(atomicObjects);
|
|
var instanceComponentsWithPath = _layerBaker.GetInstanceComponentsWithPath(instanceComponents);
|
|
|
|
// POC: 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 - Parse and bake proxies (materials and colors), as they are used later down the line by layers and objects
|
|
if (unpackedRoot.RenderMaterialProxies != null)
|
|
{
|
|
_materialBaker.ParseAndBakeRenderMaterials(
|
|
unpackedRoot.RenderMaterialProxies,
|
|
baseLayerPrefix,
|
|
onOperationProgressed
|
|
);
|
|
}
|
|
|
|
if (unpackedRoot.ColorProxies != null)
|
|
{
|
|
_colorBaker.ParseColors(unpackedRoot.ColorProxies, onOperationProgressed);
|
|
}
|
|
|
|
// 3.5 - Parse and bake additional proxies that are needed for conversion
|
|
ParseAndBakeAdditionalProxies(rootObject, baseLayerPrefix);
|
|
|
|
// 4 - Convert atomic objects
|
|
HashSet<ReceiveConversionResult> results = new();
|
|
HashSet<string> bakedObjectIds = new();
|
|
Dictionary<string, IReadOnlyCollection<Entity>> applicationIdMap = new();
|
|
var count = 0;
|
|
foreach (var (layerPath, atomicObject) in atomicObjectsWithPath)
|
|
{
|
|
onOperationProgressed.Report(new("Converting objects", (double)++count / atomicObjects.Count));
|
|
var ex = _conversionHandler.TryConvert(() =>
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
string objectId = atomicObject.applicationId ?? atomicObject.id.NotNull();
|
|
IReadOnlyCollection<Entity> convertedObjects = ConvertObject(atomicObject, layerPath, baseLayerPrefix);
|
|
|
|
applicationIdMap[objectId] = convertedObjects;
|
|
|
|
results.UnionWith(
|
|
convertedObjects.Select(e => new ReceiveConversionResult(
|
|
Status.SUCCESS,
|
|
atomicObject,
|
|
e.GetSpeckleApplicationId(),
|
|
e.GetType().ToString()
|
|
))
|
|
);
|
|
|
|
bakedObjectIds.UnionWith(convertedObjects.Select(e => e.GetSpeckleApplicationId()));
|
|
});
|
|
if (ex != null)
|
|
{
|
|
results.Add(new(Status.ERROR, atomicObject, null, null, ex));
|
|
}
|
|
}
|
|
|
|
// 5 - Convert instances
|
|
var (createdInstanceIds, consumedObjectIds, instanceConversionResults) = _instanceBaker.BakeInstances(
|
|
instanceComponentsWithPath,
|
|
applicationIdMap,
|
|
baseLayerPrefix,
|
|
onOperationProgressed
|
|
);
|
|
|
|
bakedObjectIds.RemoveWhere(id => consumedObjectIds.Contains(id));
|
|
bakedObjectIds.UnionWith(createdInstanceIds);
|
|
results.RemoveWhere(result => result.ResultId != null && consumedObjectIds.Contains(result.ResultId));
|
|
results.UnionWith(instanceConversionResults);
|
|
|
|
// 6 - Create groups
|
|
if (unpackedRoot.GroupProxies != null)
|
|
{
|
|
IReadOnlyCollection<ReceiveConversionResult> groupResults = _groupBaker.CreateGroups(
|
|
unpackedRoot.GroupProxies,
|
|
applicationIdMap
|
|
);
|
|
results.UnionWith(groupResults);
|
|
}
|
|
|
|
return Task.FromResult(new HostObjectBuilderResult(bakedObjectIds, results));
|
|
}
|
|
|
|
protected void PreReceiveDeepClean(string baseLayerPrefix)
|
|
{
|
|
_layerBaker.DeleteAllLayersByPrefix(baseLayerPrefix);
|
|
_instanceBaker.PurgeInstances(baseLayerPrefix);
|
|
_materialBaker.PurgeMaterials(baseLayerPrefix);
|
|
PreReceiveAdditionalDeepClean(baseLayerPrefix);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Method for adding app-specific additional deep clean of the document prior to receiving.
|
|
/// </summary>
|
|
protected virtual void PreReceiveAdditionalDeepClean(string baseLayerPrefix) { }
|
|
|
|
/// <summary>
|
|
/// Method for parsing and baking additional app-specific proxies on the root prior to converting and baking objects
|
|
/// </summary>
|
|
protected virtual void ParseAndBakeAdditionalProxies(Base rootObject, string baseLayerPrefix) { }
|
|
|
|
private IReadOnlyCollection<Entity> ConvertObject(Base obj, Collection[] layerPath, string baseLayerNamePrefix)
|
|
{
|
|
string layerName = _layerBaker.CreateLayerForReceive(layerPath, baseLayerNamePrefix);
|
|
var convertedEntities = new HashSet<Entity>();
|
|
|
|
using var tr = Application.DocumentManager.CurrentDocument.Database.TransactionManager.StartTransaction();
|
|
|
|
// 1: convert
|
|
var converted = _converter.Convert(obj);
|
|
|
|
// 2: handle result
|
|
switch (converted)
|
|
{
|
|
case Entity entity:
|
|
var bakedEntity = BakeObject(entity, obj, layerName, tr);
|
|
convertedEntities.Add(bakedEntity);
|
|
break;
|
|
|
|
case List<(Entity, Base)> listConversionResult: // this is from fallback conversion for brep/brepx/subdx/extrusionx/polycurve
|
|
var bakedFallbackEntities = BakeObjectsAsGroup(listConversionResult, obj, layerName, baseLayerNamePrefix, tr);
|
|
convertedEntities.UnionWith(bakedFallbackEntities);
|
|
break;
|
|
|
|
default:
|
|
// TODO: capture defualt case with report object here? Same as in Rhino
|
|
break;
|
|
}
|
|
|
|
tr.Commit();
|
|
return convertedEntities.Freeze();
|
|
}
|
|
|
|
private Entity BakeObject(
|
|
Entity entity,
|
|
Base originalObject,
|
|
string layerName,
|
|
Transaction tr,
|
|
Base? parentObject = null
|
|
)
|
|
{
|
|
var objId = originalObject.applicationId ?? originalObject.id.NotNull();
|
|
if (_colorBaker.ObjectColorsIdMap.TryGetValue(objId, out AutocadColor? color))
|
|
{
|
|
entity.Color = color;
|
|
}
|
|
|
|
if (_materialBaker.TryGetMaterialId(originalObject, parentObject, out ObjectId matId))
|
|
{
|
|
entity.MaterialId = matId;
|
|
}
|
|
|
|
entity.AppendToDb(layerName);
|
|
|
|
// Hook for derived classes to perform additional operations after entity is added to database
|
|
PostBakeEntity(entity, originalObject, tr);
|
|
|
|
return entity;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Method for additional app-specific operations on entities after the entity has been added to the document database.
|
|
/// Called after the entity is added to the database in an open transaction
|
|
/// </summary>
|
|
/// <param name="entity"></param>
|
|
/// <param name="originalObject"></param>
|
|
/// <param name="tr"></param>
|
|
protected virtual void PostBakeEntity(Entity entity, Base originalObject, Transaction tr)
|
|
{
|
|
// Default implementation does nothing - override in derived classes
|
|
}
|
|
|
|
private List<Entity> BakeObjectsAsGroup(
|
|
List<(Entity, Base)> fallbackConversionResult,
|
|
Base parentObject,
|
|
string layerName,
|
|
string baseLayerName,
|
|
Transaction tr
|
|
)
|
|
{
|
|
var ids = new ObjectIdCollection();
|
|
var entities = new List<Entity>();
|
|
foreach (var (conversionResult, originalObject) in fallbackConversionResult)
|
|
{
|
|
BakeObject(conversionResult, originalObject, layerName, tr, parentObject);
|
|
ids.Add(conversionResult.ObjectId);
|
|
entities.Add(conversionResult);
|
|
}
|
|
|
|
if (entities.Count <= 1) // return if empty list or only one, because we don't want to create empty or single item groups.
|
|
{
|
|
return entities;
|
|
}
|
|
var groupDictionary = (DBDictionary)
|
|
tr.GetObject(Application.DocumentManager.CurrentDocument.Database.GroupDictionaryId, OpenMode.ForWrite);
|
|
|
|
var groupName = _autocadContext.RemoveInvalidChars(
|
|
$@"{parentObject.speckle_type.Split('.').Last()} - {parentObject.applicationId ?? parentObject.id} ({baseLayerName})"
|
|
);
|
|
|
|
var newGroup = new Group(groupName, true);
|
|
newGroup.Append(ids);
|
|
groupDictionary.UpgradeOpen();
|
|
groupDictionary.SetAt(groupName, newGroup);
|
|
tr.AddNewlyCreatedDBObject(newGroup, true);
|
|
|
|
return entities;
|
|
}
|
|
}
|