diff --git a/Core/Core/Models/Instances/IInstanceComponent.cs b/Core/Core/Models/Instances/IInstanceComponent.cs
new file mode 100644
index 0000000000..9a6478607d
--- /dev/null
+++ b/Core/Core/Models/Instances/IInstanceComponent.cs
@@ -0,0 +1,12 @@
+namespace Speckle.Core.Models.Instances;
+
+///
+/// Abstracts over and for sorting and grouping in receive operations.
+///
+public interface IInstanceComponent
+{
+ ///
+ /// The maximum "depth" at which this or was found. On receive, as instances can be composed of other instances, we need to start from the deepest instance elements first when reconstructing them, starting with definitions first.
+ ///
+ public int MaxDepth { get; set; }
+}
diff --git a/Core/Core/Models/Instances/InstanceDefinitionProxy.cs b/Core/Core/Models/Instances/InstanceDefinitionProxy.cs
new file mode 100644
index 0000000000..0fde842faa
--- /dev/null
+++ b/Core/Core/Models/Instances/InstanceDefinitionProxy.cs
@@ -0,0 +1,16 @@
+using System.Collections.Generic;
+
+namespace Speckle.Core.Models.Instances;
+
+///
+/// A proxy class for an instance definition.
+///
+public class InstanceDefinitionProxy : Base, IInstanceComponent
+{
+ ///
+ /// The original ids of the objects that are part of this definition, as present in the source host app. On receive, they will be mapped to corresponding newly created definition ids.
+ ///
+ public List Objects { get; set; } // source app application ids for the objects
+
+ public int MaxDepth { get; set; }
+}
diff --git a/Core/Core/Models/Instances/InstanceProxy.cs b/Core/Core/Models/Instances/InstanceProxy.cs
new file mode 100644
index 0000000000..5fa18496e9
--- /dev/null
+++ b/Core/Core/Models/Instances/InstanceProxy.cs
@@ -0,0 +1,26 @@
+using System.DoubleNumerics;
+
+namespace Speckle.Core.Models.Instances;
+
+///
+/// A proxy class for an instance (e.g, a rhino block, or an autocad block reference).
+///
+public class InstanceProxy : Base, IInstanceComponent
+{
+ ///
+ /// The definition id as present in the original host app. On receive, it will be mapped to the newly created definition id.
+ ///
+ public string DefinitionId { get; set; }
+
+ ///
+ /// The transform of the instance reference.
+ ///
+ public Matrix4x4 Transform { get; set; }
+
+ ///
+ /// The units of the host application file.
+ ///
+ public string Units { get; set; } = Kits.Units.Meters;
+
+ public int MaxDepth { get; set; }
+}
diff --git a/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/packages.lock.json b/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/packages.lock.json
index e890a9f47a..b975016a9b 100644
--- a/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/packages.lock.json
+++ b/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/packages.lock.json
@@ -383,7 +383,7 @@
"Microsoft.Extensions.Logging.Abstractions": "[7.0.0, )",
"Speckle.Autofac": "[2.0.999-local, )",
"Speckle.Connectors.Utils": "[2.0.999-local, )",
- "Speckle.Core": "[3.0.1-alpha.11, )",
+ "Speckle.Core": "[3.0.1-alpha.14, )",
"System.Threading.Tasks.Dataflow": "[6.0.0, )"
}
},
@@ -399,7 +399,7 @@
"dependencies": {
"Serilog.Extensions.Logging": "[7.0.0, )",
"Speckle.Autofac": "[2.0.999-local, )",
- "Speckle.Core": "[3.0.1-alpha.11, )"
+ "Speckle.Core": "[3.0.1-alpha.14, )"
}
},
"speckle.converters.arcgis3": {
@@ -421,7 +421,7 @@
"type": "Project",
"dependencies": {
"Speckle.Autofac": "[2.0.999-local, )",
- "Speckle.Objects": "[3.0.1-alpha.11, )"
+ "Speckle.Objects": "[3.0.1-alpha.14, )"
}
},
"speckle.converters.common.dependencyinjection": {
@@ -455,9 +455,9 @@
},
"Speckle.Core": {
"type": "CentralTransitive",
- "requested": "[3.0.1-alpha.11, )",
- "resolved": "3.0.1-alpha.11",
- "contentHash": "Zt2dBJLlfziEACYCHThbhKypSjhoA01rTw9BzNI72c/BDyftXIz70Tetq/8ZMEqQnKqfmRyYADsAdWKxpdV0Hg==",
+ "requested": "[3.0.1-alpha.14, )",
+ "resolved": "3.0.1-alpha.14",
+ "contentHash": "RzQPVIGFFkKvG56YLr8ACtiwdWJE6IJ9vCQ4qHa0PIsUEpfzAIAi59jnzqtByOFC0FiFrFPow9bkfzylaZorAA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.CSharp": "4.7.0",
@@ -479,11 +479,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
- "requested": "[3.0.1-alpha.11, )",
- "resolved": "3.0.1-alpha.11",
- "contentHash": "YR7sei3OQBAi8R/kIu8bEmg5ELDIJK6l5fhOgdnqA9ZvD/fvmRb+09z8lIUTDsgdAdvYU/A/x9VxH9OGPbp9Kw==",
+ "requested": "[3.0.1-alpha.14, )",
+ "resolved": "3.0.1-alpha.14",
+ "contentHash": "z38LGryMvh7iU1uBW+4uo5DwsB3CwRgLt2uFexWFx3mPSid+A0l5XcJzOgLwgFhNl6B42Ryz4ezBsddTp1Uc/g==",
"dependencies": {
- "Speckle.Core": "3.0.1-alpha.11"
+ "Speckle.Core": "3.0.1-alpha.14"
}
},
"System.Threading.Tasks.Dataflow": {
diff --git a/DUI3-DX/Connectors/Autocad/Speckle.Connectors.Autocad2023/packages.lock.json b/DUI3-DX/Connectors/Autocad/Speckle.Connectors.Autocad2023/packages.lock.json
index 68bcf35809..0fff9ad56a 100644
--- a/DUI3-DX/Connectors/Autocad/Speckle.Connectors.Autocad2023/packages.lock.json
+++ b/DUI3-DX/Connectors/Autocad/Speckle.Connectors.Autocad2023/packages.lock.json
@@ -436,7 +436,7 @@
"Microsoft.Extensions.Logging.Abstractions": "[7.0.0, )",
"Speckle.Autofac": "[2.0.999-local, )",
"Speckle.Connectors.Utils": "[2.0.999-local, )",
- "Speckle.Core": "[3.0.1-alpha.11, )",
+ "Speckle.Core": "[3.0.1-alpha.14, )",
"System.Threading.Tasks.Dataflow": "[6.0.0, )"
}
},
@@ -452,7 +452,7 @@
"dependencies": {
"Serilog.Extensions.Logging": "[7.0.0, )",
"Speckle.Autofac": "[2.0.999-local, )",
- "Speckle.Core": "[3.0.1-alpha.11, )"
+ "Speckle.Core": "[3.0.1-alpha.14, )"
}
},
"speckle.converters.autocad2023": {
@@ -474,7 +474,7 @@
"type": "Project",
"dependencies": {
"Speckle.Autofac": "[2.0.999-local, )",
- "Speckle.Objects": "[3.0.1-alpha.11, )"
+ "Speckle.Objects": "[3.0.1-alpha.14, )"
}
},
"speckle.converters.common.dependencyinjection": {
@@ -511,9 +511,9 @@
},
"Speckle.Core": {
"type": "CentralTransitive",
- "requested": "[3.0.1-alpha.11, )",
- "resolved": "3.0.1-alpha.11",
- "contentHash": "Zt2dBJLlfziEACYCHThbhKypSjhoA01rTw9BzNI72c/BDyftXIz70Tetq/8ZMEqQnKqfmRyYADsAdWKxpdV0Hg==",
+ "requested": "[3.0.1-alpha.14, )",
+ "resolved": "3.0.1-alpha.14",
+ "contentHash": "RzQPVIGFFkKvG56YLr8ACtiwdWJE6IJ9vCQ4qHa0PIsUEpfzAIAi59jnzqtByOFC0FiFrFPow9bkfzylaZorAA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.CSharp": "4.7.0",
@@ -535,11 +535,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
- "requested": "[3.0.1-alpha.11, )",
- "resolved": "3.0.1-alpha.11",
- "contentHash": "YR7sei3OQBAi8R/kIu8bEmg5ELDIJK6l5fhOgdnqA9ZvD/fvmRb+09z8lIUTDsgdAdvYU/A/x9VxH9OGPbp9Kw==",
+ "requested": "[3.0.1-alpha.14, )",
+ "resolved": "3.0.1-alpha.14",
+ "contentHash": "z38LGryMvh7iU1uBW+4uo5DwsB3CwRgLt2uFexWFx3mPSid+A0l5XcJzOgLwgFhNl6B42Ryz4ezBsddTp1Uc/g==",
"dependencies": {
- "Speckle.Core": "3.0.1-alpha.11"
+ "Speckle.Core": "3.0.1-alpha.14"
}
},
"System.Threading.Tasks.Dataflow": {
diff --git a/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadBasicConnectorBinding.cs b/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadBasicConnectorBinding.cs
index a87ea7f70d..a30179a2cb 100644
--- a/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadBasicConnectorBinding.cs
+++ b/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadBasicConnectorBinding.cs
@@ -8,6 +8,7 @@
using Speckle.Core.Credentials;
using Speckle.Connectors.Autocad.HostApp.Extensions;
using Speckle.Connectors.Utils;
+using Speckle.Core.Logging;
namespace Speckle.Connectors.Autocad.Bindings;
@@ -108,34 +109,50 @@ public void HighlightModel(string modelCardId)
return;
}
- HighlightObjectsOnView(objectIds);
+ HighlightObjectsOnView(objectIds, modelCardId);
}
- private void HighlightObjectsOnView(ObjectId[] objectIds)
+ private void HighlightObjectsOnView(ObjectId[] objectIds, string? modelCardId = null)
{
var doc = Application.DocumentManager.MdiActiveDocument;
Parent.RunOnMainThread(() =>
{
- doc.Editor.SetImpliedSelection(Array.Empty()); // Deselects
- doc.Editor.SetImpliedSelection(objectIds); // Selects
- doc.Editor.UpdateScreen();
+ try
+ {
+ doc.Editor.SetImpliedSelection(Array.Empty()); // Deselects
+ doc.Editor.SetImpliedSelection(objectIds); // Selects
+ doc.Editor.UpdateScreen();
+
+ Extents3d selectedExtents = new();
- Extents3d selectedExtents = new();
+ var tr = doc.TransactionManager.StartTransaction();
+ foreach (ObjectId objectId in objectIds)
+ {
+ var entity = (Entity)tr.GetObject(objectId, OpenMode.ForRead);
+ if (entity != null)
+ {
+ selectedExtents.AddExtents(entity.GeometricExtents);
+ }
+ }
- var tr = doc.TransactionManager.StartTransaction();
- foreach (ObjectId objectId in objectIds)
+ doc.Editor.Zoom(selectedExtents);
+ tr.Commit();
+ Autodesk.AutoCAD.Internal.Utils.FlushGraphics();
+ }
+ catch (Exception ex) when (!ex.IsFatal())
{
- var entity = (Entity)tr.GetObject(objectId, OpenMode.ForRead);
- if (entity != null)
+ if (modelCardId != null)
+ {
+ Commands.SetModelError(modelCardId, new OperationCanceledException("Failed to highlight objects."));
+ }
+ else
{
- selectedExtents.AddExtents(entity.GeometricExtents);
+ // This will happen, in some cases, where we highlight individual objects. Should be caught by the top level handler and not
+ // crash the host app.
+ throw;
}
}
-
- doc.Editor.Zoom(selectedExtents);
- tr.Commit();
- Autodesk.AutoCAD.Internal.Utils.FlushGraphics();
});
}
}
diff --git a/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/DependencyInjection/AutocadConnectorModule.cs b/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/DependencyInjection/AutocadConnectorModule.cs
index 731098b249..52da8c7d75 100644
--- a/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/DependencyInjection/AutocadConnectorModule.cs
+++ b/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/DependencyInjection/AutocadConnectorModule.cs
@@ -1,13 +1,23 @@
#if AUTOCAD
+using Autodesk.AutoCAD.DatabaseServices;
+using Speckle.Autofac;
using Speckle.Autofac.DependencyInjection;
using Speckle.Connectors.Autocad.Bindings;
using Speckle.Connectors.Autocad.Filters;
+using Speckle.Connectors.Autocad.HostApp;
+using Speckle.Connectors.Autocad.Interfaces;
using Speckle.Connectors.Autocad.Operations.Receive;
using Speckle.Connectors.Autocad.Operations.Send;
+using Speckle.Connectors.Autocad.Plugin;
+using Speckle.Connectors.DUI;
using Speckle.Connectors.DUI.Bindings;
+using Speckle.Connectors.DUI.Models;
using Speckle.Connectors.DUI.Models.Card.SendFilter;
+using Speckle.Connectors.DUI.WebView;
+using Speckle.Connectors.Utils;
using Speckle.Connectors.Utils.Builders;
using Speckle.Connectors.Utils.Caching;
+using Speckle.Connectors.Utils.Instances;
using Speckle.Connectors.Utils.Operations;
using Speckle.Core.Models.GraphTraversal;
@@ -17,8 +27,24 @@ public class AutocadConnectorModule : ISpeckleModule
{
public void Load(SpeckleContainerBuilder builder)
{
+ builder.AddAutofac();
+ builder.AddConnectorUtils();
+ builder.AddDUI();
+ builder.AddDUIView();
+
+ // Register other connector specific types
+ builder.AddSingleton();
+ builder.AddTransient();
+ builder.AddSingleton(new AutocadDocumentManager()); // TODO: Dependent to TransactionContext, can be moved to AutocadContext
+ builder.AddSingleton();
+ builder.AddSingleton();
+ builder.AddSingleton();
+
SharedConnectorModule.LoadShared(builder);
+
+ builder.AddScoped();
+
// Operations
builder.AddScoped>();
builder.AddSingleton(DefaultTraversal.CreateTraversalFunc());
@@ -37,6 +63,7 @@ public void Load(SpeckleContainerBuilder builder)
// register send conversion cache
builder.AddSingleton();
+ builder.AddScoped>, AutocadInstanceObjectManager>();
}
}
#endif
diff --git a/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/HostApp/AutocadInstanceObjectManager.cs b/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/HostApp/AutocadInstanceObjectManager.cs
new file mode 100644
index 0000000000..11067f52ba
--- /dev/null
+++ b/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/HostApp/AutocadInstanceObjectManager.cs
@@ -0,0 +1,368 @@
+using System.DoubleNumerics;
+using Autodesk.AutoCAD.DatabaseServices;
+using Autodesk.AutoCAD.Geometry;
+using Speckle.Connectors.Autocad.HostApp.Extensions;
+using Speckle.Connectors.Autocad.Operations.Send;
+using Speckle.Connectors.Utils.Conversion;
+using Speckle.Connectors.Utils.Instances;
+using Speckle.Core.Kits;
+using Speckle.Core.Logging;
+using Speckle.Core.Models;
+using Speckle.Core.Models.Instances;
+
+namespace Speckle.Connectors.Autocad.HostApp;
+
+///
+///
+/// Expects to be a scoped dependency per send or receive operation.
+///
+public class AutocadInstanceObjectManager : IInstanceObjectsManager>
+{
+ private readonly AutocadLayerManager _autocadLayerManager;
+ private Dictionary InstanceProxies { get; set; } = new();
+ private Dictionary> InstanceProxiesByDefinitionId { get; set; } = new();
+ private Dictionary DefinitionProxies { get; set; } = new();
+ private Dictionary FlatAtomicObjects { get; set; } = new();
+
+ public AutocadInstanceObjectManager(AutocadLayerManager autocadLayerManager)
+ {
+ _autocadLayerManager = autocadLayerManager;
+ }
+
+ public UnpackResult UnpackSelection(IEnumerable objects)
+ {
+ using var transaction = Application.DocumentManager.CurrentDocument.Database.TransactionManager.StartTransaction();
+
+ foreach (var obj in objects)
+ {
+ if (obj.Root is BlockReference blockReference && !blockReference.IsDynamicBlock)
+ {
+ UnpackInstance(blockReference, 0, transaction);
+ }
+
+ FlatAtomicObjects[obj.ApplicationId] = obj;
+ }
+ return new(FlatAtomicObjects.Values.ToList(), InstanceProxies, DefinitionProxies.Values.ToList());
+ }
+
+ private void UnpackInstance(BlockReference instance, int depth, Transaction transaction)
+ {
+ var instanceIdString = instance.Handle.Value.ToString();
+ var definitionId = instance.BlockTableRecord;
+
+ InstanceProxies[instanceIdString] = new InstanceProxy()
+ {
+ applicationId = instanceIdString,
+ DefinitionId = definitionId.ToString(),
+ MaxDepth = depth,
+ Transform = GetMatrix(instance.BlockTransform.ToArray()),
+ Units = Application.DocumentManager.CurrentDocument.Database.Insunits.ToSpeckleString()
+ };
+
+ // For each block instance that has the same definition, we need to keep track of the "maximum depth" at which is found.
+ // This will enable on receive to create them in the correct order (descending by max depth, interleaved definitions and instances).
+ // We need to interleave the creation of definitions and instances, as some definitions may depend on instances.
+ if (
+ !InstanceProxiesByDefinitionId.TryGetValue(
+ definitionId.ToString(),
+ out List instanceProxiesWithSameDefinition
+ )
+ )
+ {
+ instanceProxiesWithSameDefinition = new List();
+ InstanceProxiesByDefinitionId[definitionId.ToString()] = instanceProxiesWithSameDefinition;
+ }
+
+ // We ensure that all previous instance proxies that have the same definition are at this max depth. I kind of have a feeling this can be done more elegantly, but YOLO
+ foreach (var instanceProxy in instanceProxiesWithSameDefinition)
+ {
+ instanceProxy.MaxDepth = depth;
+ }
+
+ instanceProxiesWithSameDefinition.Add(InstanceProxies[instanceIdString]);
+
+ if (DefinitionProxies.TryGetValue(definitionId.ToString(), out InstanceDefinitionProxy value))
+ {
+ value.MaxDepth = depth;
+ return; // exit fast - we've parsed this one so no need to go further
+ }
+
+ var definition = (BlockTableRecord)transaction.GetObject(definitionId, OpenMode.ForRead);
+ // definition.Origin
+ var definitionProxy = new InstanceDefinitionProxy()
+ {
+ applicationId = definitionId.ToString(),
+ Objects = new(),
+ MaxDepth = depth,
+ ["name"] = definition.Name,
+ ["comments"] = definition.Comments,
+ ["units"] = definition.Units // ? not sure needed?
+ };
+
+ // Go through each definition object
+ foreach (ObjectId id in definition)
+ {
+ var obj = transaction.GetObject(id, OpenMode.ForRead);
+ var handleIdString = obj.Handle.Value.ToString();
+ definitionProxy.Objects.Add(handleIdString);
+
+ if (obj is BlockReference blockReference && !blockReference.IsDynamicBlock)
+ {
+ UnpackInstance(blockReference, depth + 1, transaction);
+ }
+ FlatAtomicObjects[handleIdString] = new(obj, handleIdString);
+ }
+
+ DefinitionProxies[definitionId.ToString()] = definitionProxy;
+ }
+
+ public BakeResult BakeInstances(
+ List<(string[] layerPath, IInstanceComponent obj)> instanceComponents,
+ Dictionary> applicationIdMap,
+ string baseLayerName,
+ Action? onOperationProgressed
+ )
+ {
+ var sortedInstanceComponents = instanceComponents
+ .OrderByDescending(x => x.obj.MaxDepth) // Sort by max depth, so we start baking from the deepest element first
+ .ThenBy(x => x.obj is InstanceDefinitionProxy ? 0 : 1) // Ensure we bake the deepest definition first, then any instances that depend on it
+ .ToList();
+
+ var definitionIdAndApplicationIdMap = new Dictionary();
+
+ using var transaction = Application.DocumentManager.CurrentDocument.Database.TransactionManager.StartTransaction();
+ var conversionResults = new List();
+ var createdObjectIds = new List();
+ var consumedObjectIds = new List();
+ var count = 0;
+
+ foreach (var (path, instanceOrDefinition) in sortedInstanceComponents)
+ {
+ try
+ {
+ onOperationProgressed?.Invoke("Converting blocks", (double)++count / sortedInstanceComponents.Count);
+ if (instanceOrDefinition is InstanceDefinitionProxy { applicationId: not null } definitionProxy)
+ {
+ // TODO: create definition (block table record)
+ var constituentEntities = definitionProxy.Objects
+ .Select(id => applicationIdMap.TryGetValue(id, out List value) ? value : null)
+ .Where(x => x is not null)
+ .SelectMany(ent => ent)
+ .ToList();
+
+ var record = new BlockTableRecord();
+ var objectIds = new ObjectIdCollection();
+ record.Name = baseLayerName;
+ if (definitionProxy["name"] is string name)
+ {
+ record.Name += name;
+ }
+ else
+ {
+ record.Name += definitionProxy.applicationId;
+ }
+
+ foreach (var entity in constituentEntities)
+ {
+ // record.AppendEntity(entity);
+ objectIds.Add(entity.ObjectId);
+ }
+
+ using var blockTable = (BlockTable)
+ transaction.GetObject(Application.DocumentManager.CurrentDocument.Database.BlockTableId, OpenMode.ForWrite);
+ var id = blockTable.Add(record);
+ record.AssumeOwnershipOf(objectIds);
+
+ definitionIdAndApplicationIdMap[definitionProxy.applicationId] = id;
+ transaction.AddNewlyCreatedDBObject(record, true);
+ var consumedEntitiesHandleValues = constituentEntities.Select(ent => ent.Handle.Value.ToString()).ToArray();
+ consumedObjectIds.AddRange(consumedEntitiesHandleValues);
+ createdObjectIds.RemoveAll(newId => consumedEntitiesHandleValues.Contains(newId));
+ }
+ else if (
+ instanceOrDefinition is InstanceProxy instanceProxy
+ && definitionIdAndApplicationIdMap.TryGetValue(instanceProxy.DefinitionId, out ObjectId definitionId)
+ )
+ {
+ var matrix3d = GetMatrix3d(instanceProxy.Transform, instanceProxy.Units);
+ var insertionPoint = Point3d.Origin.TransformBy(matrix3d);
+
+ var modelSpaceBlockTableRecord = Application.DocumentManager.CurrentDocument.Database.GetModelSpace(
+ OpenMode.ForWrite
+ );
+ _autocadLayerManager.CreateLayerForReceive(path[0]);
+ var blockRef = new BlockReference(insertionPoint, definitionId)
+ {
+ BlockTransform = matrix3d,
+ Layer = path[0],
+ };
+
+ modelSpaceBlockTableRecord.AppendEntity(blockRef);
+
+ if (instanceProxy.applicationId != null)
+ {
+ applicationIdMap[instanceProxy.applicationId] = new List { blockRef };
+ }
+
+ transaction.AddNewlyCreatedDBObject(blockRef, true);
+ conversionResults.Add(
+ new(Status.SUCCESS, instanceProxy, blockRef.Handle.Value.ToString(), "Instance (Block)")
+ );
+ createdObjectIds.Add(blockRef.Handle.Value.ToString());
+ }
+ }
+ catch (Exception ex) when (!ex.IsFatal())
+ {
+ conversionResults.Add(new(Status.ERROR, instanceOrDefinition as Base ?? new Base(), null, null, ex));
+ }
+ }
+ transaction.Commit();
+ return new(createdObjectIds, consumedObjectIds, conversionResults);
+ }
+
+ ///
+ /// Cleans up any previously created instances.
+ /// POC: This function will not be able to delete block definitions if the user creates a new one composed out of received definitions.
+ ///
+ ///
+ public void PurgeInstances(string namePrefix)
+ {
+ using var transaction = Application.DocumentManager.CurrentDocument.Database.TransactionManager.StartTransaction();
+ var instanceDefinitionsToDelete = new Dictionary();
+
+ // Helper function that recurses through a given block table record's constituent objects and purges inner instances as required.
+ void TraverseAndClean(BlockTableRecord btr)
+ {
+ foreach (var objectId in btr)
+ {
+ var obj = transaction.GetObject(objectId, OpenMode.ForRead) as BlockReference;
+ if (obj == null)
+ {
+ continue;
+ }
+ var definition = (BlockTableRecord)transaction.GetObject(obj.BlockTableRecord, OpenMode.ForRead);
+ if (obj.IsErased)
+ {
+ TraverseAndClean(definition);
+ continue;
+ }
+
+ obj.UpgradeOpen();
+ obj.Erase();
+ TraverseAndClean(definition);
+ instanceDefinitionsToDelete[obj.BlockTableRecord.ToString()] = definition;
+ }
+ }
+
+ using var blockTable = (BlockTable)
+ transaction.GetObject(Application.DocumentManager.CurrentDocument.Database.BlockTableId, OpenMode.ForRead);
+
+ // deep clean definitions
+ foreach (var btrId in blockTable)
+ {
+ var btr = (BlockTableRecord)transaction.GetObject(btrId, OpenMode.ForRead);
+ if (btr.Name.Contains(namePrefix)) // POC: this is tightly coupled with a naming convention for definitions in the instance object manager
+ {
+ TraverseAndClean(btr);
+ instanceDefinitionsToDelete[btr.Name] = btr;
+ }
+ }
+
+ foreach (var def in instanceDefinitionsToDelete.Values)
+ {
+ def.UpgradeOpen();
+ def.Erase();
+ }
+
+ transaction.Commit();
+ }
+
+ private Matrix4x4 GetMatrix(double[] t)
+ {
+ return new Matrix4x4(
+ t[0],
+ t[1],
+ t[2],
+ t[3],
+ t[4],
+ t[5],
+ t[6],
+ t[7],
+ t[8],
+ t[9],
+ t[10],
+ t[11],
+ t[12],
+ t[13],
+ t[14],
+ t[15]
+ );
+ }
+
+ private Matrix3d GetMatrix3d(Matrix4x4 matrix, string units)
+ {
+ var sf = Units.GetConversionFactor(
+ units,
+ Application.DocumentManager.CurrentDocument.Database.Insunits.ToSpeckleString()
+ );
+
+ var scaledTransform = new[]
+ {
+ matrix.M11,
+ matrix.M12,
+ matrix.M13,
+ matrix.M14 * sf,
+ matrix.M21,
+ matrix.M22,
+ matrix.M23,
+ matrix.M24 * sf,
+ matrix.M31,
+ matrix.M32,
+ matrix.M33,
+ matrix.M34 * sf,
+ matrix.M41,
+ matrix.M42,
+ matrix.M43,
+ matrix.M44
+ };
+
+ var m3d = new Matrix3d(scaledTransform);
+ if (!m3d.IsScaledOrtho())
+ {
+ m3d = new Matrix3d(MakePerpendicular(m3d));
+ }
+
+ return m3d;
+ }
+
+ // https://forums.autodesk.com/t5/net/set-blocktransform-values/m-p/6452121#M49479
+ private static double[] MakePerpendicular(Matrix3d matrix)
+ {
+ // Get the basis vectors of the matrix
+ Vector3d right = new(matrix[0, 0], matrix[1, 0], matrix[2, 0]);
+ Vector3d up = new(matrix[0, 1], matrix[1, 1], matrix[2, 1]);
+
+ Vector3d newForward = right.CrossProduct(up).GetNormal();
+ Vector3d newUp = newForward.CrossProduct(right).GetNormal();
+
+ return new[]
+ {
+ right.X,
+ newUp.X,
+ newForward.X,
+ matrix[0, 3],
+ right.Y,
+ newUp.Y,
+ newForward.Y,
+ matrix[1, 3],
+ right.Z,
+ newUp.Z,
+ newForward.Z,
+ matrix[2, 3],
+ 0.0,
+ 0.0,
+ 0.0,
+ matrix[3, 3],
+ };
+ }
+}
diff --git a/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/HostApp/AutocadLayerManager.cs b/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/HostApp/AutocadLayerManager.cs
index 87b1f00ca7..d172bea860 100644
--- a/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/HostApp/AutocadLayerManager.cs
+++ b/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/HostApp/AutocadLayerManager.cs
@@ -1,9 +1,14 @@
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.LayerManager;
+using Speckle.Core.Models;
+using Speckle.Core.Models.GraphTraversal;
namespace Speckle.Connectors.Autocad.HostApp;
+///
+/// Expects to be a scoped dependency for a given operation and helps with layer creation and cleanup.
+///
public class AutocadLayerManager
{
private readonly AutocadContext _autocadContext;
@@ -11,38 +16,30 @@ public class AutocadLayerManager
// POC: Will be addressed to move it into AutocadContext!
private Document Doc => Application.DocumentManager.MdiActiveDocument;
+ private readonly HashSet _uniqueLayerNames = new();
public AutocadLayerManager(AutocadContext autocadContext)
{
_autocadContext = autocadContext;
}
- ///
- /// Constructs layer name with prefix and valid characters.
- ///
- /// Prefix to add layer name.
- /// list of entries to concat with hyphen.
- /// Full layer name with provided prefix and path.
- public string LayerFullName(string baseLayerPrefix, string path)
- {
- var layerFullName = baseLayerPrefix + string.Join("-", path);
- return _autocadContext.RemoveInvalidChars(layerFullName);
- }
-
///
/// Will create a layer with the provided name, or, if it finds an existing one, will "purge" all objects from it.
/// This ensures we're creating the new objects we've just received rather than overlaying them.
///
/// Name to search layer for purge and create.
- public void CreateLayerOrPurge(string layerName)
+ public void CreateLayerForReceive(string layerName)
{
- // POC: Will be addressed to move it into AutocadContext!
- Document doc = Application.DocumentManager.MdiActiveDocument;
- doc.LockDocument();
- using Transaction transaction = doc.TransactionManager.StartTransaction();
+ if (!_uniqueLayerNames.Add(layerName))
+ {
+ return;
+ }
+
+ Doc.LockDocument();
+ using Transaction transaction = Doc.TransactionManager.StartTransaction();
LayerTable? layerTable =
- transaction.TransactionManager.GetObject(doc.Database.LayerTableId, OpenMode.ForRead) as LayerTable;
+ transaction.TransactionManager.GetObject(Doc.Database.LayerTableId, OpenMode.ForRead) as LayerTable;
LayerTableRecord layerTableRecord = new() { Name = layerName };
bool hasLayer = layerTable != null && layerTable.Has(layerName);
@@ -50,7 +47,7 @@ public void CreateLayerOrPurge(string layerName)
{
TypedValue[] tvs = { new((int)DxfCode.LayerName, layerName) };
SelectionFilter selectionFilter = new(tvs);
- SelectionSet selectionResult = doc.Editor.SelectAll(selectionFilter).Value;
+ SelectionSet selectionResult = Doc.Editor.SelectAll(selectionFilter).Value;
if (selectionResult == null)
{
return;
@@ -69,7 +66,38 @@ public void CreateLayerOrPurge(string layerName)
transaction.Commit();
}
- // POC: Consider to extract somehow in factory or service!
+ public void DeleteAllLayersByPrefix(string prefix)
+ {
+ Doc.LockDocument();
+ using Transaction transaction = Doc.TransactionManager.StartTransaction();
+
+ var layerTable = (LayerTable)transaction.TransactionManager.GetObject(Doc.Database.LayerTableId, OpenMode.ForRead);
+ foreach (var layerId in layerTable)
+ {
+ var layer = (LayerTableRecord)transaction.GetObject(layerId, OpenMode.ForRead);
+ var layerName = layer.Name;
+ if (layer.Name.Contains(prefix))
+ {
+ // Delete objects from this layer
+ TypedValue[] tvs = { new((int)DxfCode.LayerName, layerName) };
+ SelectionFilter selectionFilter = new(tvs);
+ SelectionSet selectionResult = Doc.Editor.SelectAll(selectionFilter).Value;
+ if (selectionResult == null)
+ {
+ return;
+ }
+ foreach (SelectedObject selectedObject in selectionResult)
+ {
+ transaction.GetObject(selectedObject.ObjectId, OpenMode.ForWrite).Erase();
+ }
+ // Delete layer
+ layer.UpgradeOpen();
+ layer.Erase();
+ }
+ }
+ transaction.Commit();
+ }
+
///
/// Creates a layer filter for the just received model, grouped under a top level filter "Speckle". Note: manual close and open of the layer properties panel required (it's an acad thing).
/// This comes in handy to quickly access the layers created for this specific model.
@@ -114,4 +142,19 @@ public void CreateLayerFilter(string projectName, string modelName)
groupFilter.NestedFilters.Add(layerFilter);
Doc.Database.LayerFilters = layerFilterTree;
}
+
+ ///
+ /// Gets a valid layer name for a given context.
+ ///
+ ///
+ ///
+ ///
+ public string GetLayerPath(TraversalContext context, string baseLayerPrefix)
+ {
+ string[] collectionBasedPath = context.GetAscendantOfType().Select(c => c.name).Reverse().ToArray();
+ string[] path = collectionBasedPath.Length != 0 ? collectionBasedPath : context.GetPropertyPath().ToArray();
+
+ var name = baseLayerPrefix + string.Join("-", path);
+ return _autocadContext.RemoveInvalidChars(name);
+ }
}
diff --git a/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/HostApp/Extensions/AcadUnitsExtension.cs b/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/HostApp/Extensions/AcadUnitsExtension.cs
new file mode 100644
index 0000000000..f4ee8e3a66
--- /dev/null
+++ b/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/HostApp/Extensions/AcadUnitsExtension.cs
@@ -0,0 +1,39 @@
+using Autodesk.AutoCAD.DatabaseServices;
+using Speckle.Core.Kits;
+using Speckle.Core.Logging;
+
+namespace Speckle.Connectors.Autocad.HostApp.Extensions;
+
+public static class AcadUnitsExtension
+{
+ public static string ToSpeckleString(this UnitsValue units)
+ {
+ switch (units)
+ {
+ case UnitsValue.Millimeters:
+ return Units.Millimeters;
+ case UnitsValue.Centimeters:
+ return Units.Centimeters;
+ case UnitsValue.Meters:
+ return Units.Meters;
+ case UnitsValue.Kilometers:
+ return Units.Kilometers;
+ case UnitsValue.Inches:
+ case UnitsValue.USSurveyInch:
+ return Units.Inches;
+ case UnitsValue.Feet:
+ case UnitsValue.USSurveyFeet:
+ return Units.Feet;
+ case UnitsValue.Yards:
+ case UnitsValue.USSurveyYard:
+ return Units.Yards;
+ case UnitsValue.Miles:
+ case UnitsValue.USSurveyMile:
+ return Units.Miles;
+ case UnitsValue.Undefined:
+ return Units.None;
+ default:
+ throw new SpeckleException($"The Unit System \"{units}\" is unsupported.");
+ }
+ }
+}
diff --git a/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Operations/Receive/AutocadHostObjectBuilder.cs b/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Operations/Receive/AutocadHostObjectBuilder.cs
index e5b720aad7..3e2e44c0fe 100644
--- a/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Operations/Receive/AutocadHostObjectBuilder.cs
+++ b/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Operations/Receive/AutocadHostObjectBuilder.cs
@@ -1,30 +1,41 @@
using Autodesk.AutoCAD.DatabaseServices;
using Speckle.Connectors.Autocad.HostApp;
using Speckle.Connectors.Autocad.HostApp.Extensions;
+using Speckle.Connectors.Autocad.Operations.Send;
using Speckle.Core.Models;
using Speckle.Connectors.Utils.Builders;
using Speckle.Connectors.Utils.Conversion;
+using Speckle.Connectors.Utils.Instances;
using Speckle.Converters.Common;
using Speckle.Core.Logging;
using Speckle.Core.Models.GraphTraversal;
+using Speckle.Core.Models.Instances;
namespace Speckle.Connectors.Autocad.Operations.Receive;
+///
+/// Expects to be a scoped dependency per receive operation.
+///
public class AutocadHostObjectBuilder : IHostObjectBuilder
{
private readonly AutocadLayerManager _autocadLayerManager;
private readonly IRootToHostConverter _converter;
private readonly GraphTraversal _traversalFunction;
+ // private readonly HashSet _uniqueLayerNames = new();
+ private readonly IInstanceObjectsManager> _instanceObjectsManager;
+
public AutocadHostObjectBuilder(
IRootToHostConverter converter,
GraphTraversal traversalFunction,
- AutocadLayerManager autocadLayerManager
+ AutocadLayerManager autocadLayerManager,
+ IInstanceObjectsManager> instanceObjectsManager
)
{
_converter = converter;
_traversalFunction = traversalFunction;
_autocadLayerManager = autocadLayerManager;
+ _instanceObjectsManager = instanceObjectsManager;
}
public HostObjectBuilderResult Build(
@@ -43,20 +54,68 @@ CancellationToken cancellationToken
//TODO: make the layerManager handle \/ ?
string baseLayerPrefix = $"SPK-{projectName}-{modelName}-";
- HashSet uniqueLayerNames = new();
+
+ PreReceiveDeepClean(baseLayerPrefix);
List results = new();
List bakedObjectIds = new();
- foreach (var tc in _traversalFunction.TraverseWithProgress(rootObject, onOperationProgressed, cancellationToken))
+
+ // return new(bakedObjectIds, results);
+
+ var objectGraph = _traversalFunction.Traverse(rootObject).Where(obj => obj.Current is not Collection);
+
+ // POC: these are not captured by traversal, so we need to re-add them here
+ var instanceDefinitionProxies = (rootObject["instanceDefinitionProxies"] as List