Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 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 @@ -11,6 +11,13 @@ namespace Speckle.Connectors.Autocad.HostApp;
/// </summary>
public class AutocadGroupManager
{
private readonly AutocadContext _autocadContext;

public AutocadGroupManager(AutocadContext autocadContext)
{
_autocadContext = autocadContext;
}

/// <summary>
/// Unpacks a selection of atomic objects into their groups
/// </summary>
Expand Down Expand Up @@ -86,11 +93,12 @@ Dictionary<string, List<Entity>> applicationIdMap
ids.Add(entity.ObjectId);
}

var newGroup = new Group(gp.name, true); // NOTE: this constructor sets both the description (as it says) but also the name at the same time
var groupName = _autocadContext.RemoveInvalidChars(gp.name);
var newGroup = new Group(groupName, true); // NOTE: this constructor sets both the description (as it says) but also the name at the same time
newGroup.Append(ids);

groupDictionary.UpgradeOpen();
groupDictionary.SetAt(gp.name, newGroup);
groupDictionary.SetAt(groupName, newGroup);

groupCreationTransaction.AddNewlyCreatedDBObject(newGroup, true);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,10 @@ Transaction tr
// POC: Currently we're relying on the render material name for identification if it's coming from speckle and from which model; could we do something else?
// POC: we should assume render materials all have application ids?
string renderMaterialId = renderMaterial.applicationId ?? renderMaterial.id;
string matName = $"{renderMaterial.name}-({renderMaterialId})-{baseLayerPrefix}";
string matName = _autocadContext.RemoveInvalidChars(
$"{renderMaterial.name}-({renderMaterialId})-{baseLayerPrefix}"
);

MaterialMap map = new();
MaterialOpacityComponent opacity = new(renderMaterial.opacity, map);
var systemDiffuse = System.Drawing.Color.FromArgb(renderMaterial.diffuse);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,47 +203,82 @@ private void PreReceiveDeepClean(string baseLayerPrefix)
private IEnumerable<Entity> ConvertObject(Base obj, Collection[] layerPath, string baseLayerNamePrefix)
{
string layerName = _autocadLayerManager.CreateLayerForReceive(layerPath, baseLayerNamePrefix);
var convertedEntities = new List<Entity>();

using (var tr = Application.DocumentManager.CurrentDocument.Database.TransactionManager.StartTransaction())
{
var converted = _converter.Convert(obj);
using var tr = Application.DocumentManager.CurrentDocument.Database.TransactionManager.StartTransaction();

IEnumerable<Entity?> flattened = Utilities.FlattenToHostConversionResult(converted).Cast<Entity>();
// 1: convert
var converted = _converter.Convert(obj);

// get color and material if any
string objId = obj.applicationId ?? obj.id;
AutocadColor? objColor = _colorManager.ObjectColorsIdMap.TryGetValue(objId, out AutocadColor? color)
? color
: null;
ObjectId objMaterial = _materialManager.ObjectMaterialsIdMap.TryGetValue(objId, out ObjectId matId)
? matId
: ObjectId.Null;
// 2: handle result
if (converted is Entity entity)
{
var bakedEntity = BakeObject(entity, obj, layerName);
convertedEntities.Add(bakedEntity);
}
else if (converted is IEnumerable<(object, Base)> fallbackConversionResult)
{
var bakedFallbackEntities = BakeObjectsAsGroup(fallbackConversionResult, obj, layerName, baseLayerNamePrefix);
convertedEntities.AddRange(bakedFallbackEntities);
}

foreach (Entity? conversionResult in flattened)
{
if (conversionResult == null)
{
// POC: This needed to be double checked why we check null and continue
continue;
}
tr.Commit();
return convertedEntities;
}

// set color and material
// POC: if these are displayvalue meshes, we will need to check for their ids somehow
if (objColor is not null)
{
conversionResult.Color = objColor;
}
private Entity BakeObject(Entity entity, Base originalObject, string layerName)
{
var objId = originalObject.applicationId ?? originalObject.id;
if (_colorManager.ObjectColorsIdMap.TryGetValue(objId, out AutocadColor? color))
{
entity.Color = color;
}

if (objMaterial != ObjectId.Null)
{
conversionResult.MaterialId = objMaterial;
}
if (_materialManager.ObjectMaterialsIdMap.TryGetValue(objId, out ObjectId matId))
{
entity.MaterialId = matId;
}

conversionResult.AppendToDb(layerName);
yield return conversionResult;
entity.AppendToDb(layerName);
return entity;
}

private List<Entity> BakeObjectsAsGroup(
IEnumerable<(object, Base)> fallbackConversionResult,
Base originatingObject,
string layerName,
string baseLayerName
)
{
var ids = new ObjectIdCollection();
var entities = new List<Entity>();
foreach (var (conversionResult, originalBaseObject) in fallbackConversionResult)
{
if (conversionResult is not Entity entity)
{
// TODO: throw?
continue;
}

tr.Commit();
BakeObject(entity, originalBaseObject, layerName);
ids.Add(entity.ObjectId);
entities.Add(entity);
}

var tr = Application.DocumentManager.CurrentDocument.Database.TransactionManager.TopTransaction;
var groupDictionary = (DBDictionary)
tr.GetObject(Application.DocumentManager.CurrentDocument.Database.GroupDictionaryId, OpenMode.ForWrite);

var groupName = _autocadContext.RemoveInvalidChars(
$@"{originatingObject.speckle_type.Split('.').Last()} - {originatingObject.applicationId ?? originatingObject.id} ({baseLayerName})"
);

var newGroup = new Group(groupName, true);
newGroup.Append(ids);
groupDictionary.UpgradeOpen();
groupDictionary.SetAt(groupName, newGroup);
tr.AddNewlyCreatedDBObject(newGroup, true);

return entities;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public void Load(SpeckleContainerBuilder builder)

// send operation and dependencies
builder.AddScoped<SendOperation<ElementId>>();
builder.AddScoped<SendSelectionUnpacker>();
builder.AddScoped<ElementUnpacker>();
builder.AddScoped<SendCollectionManager>();
builder.AddScoped<IRootObjectBuilder<ElementId>, RevitRootObjectBuilder>();
builder.AddSingleton<ISendConversionCache, SendConversionCache>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
using Autodesk.Revit.DB;
using Speckle.Converters.RevitShared.Helpers;

namespace Speckle.Connectors.Revit.HostApp;

/// <summary>
/// Class that unpacks a given set of selection elements into atomic objects.
/// </summary>
public class ElementUnpacker
{
private readonly IRevitConversionContextStack _contextStack;

public ElementUnpacker(IRevitConversionContextStack contextStack)
{
_contextStack = contextStack;
}

/// <summary>
/// Unpacks a random set of revit objects into atomic objects. It currently unpacks groups recurisvely, nested families into atomic family instances.
/// This method will also "pack" curtain walls if necessary (ie, if mullions or panels are selected without their parent curtain wall, they are sent independently; if the parent curtain wall is selected, they will be removed out as the curtain wall will include all its children).
/// </summary>
/// <param name="selectionElements"></param>
/// <returns></returns>
public IEnumerable<Element> UnpackSelectionForConversion(IEnumerable<Element> selectionElements)
{
// Note: steps kept separate on purpose.
// Step 1: unpack groups
var atomicObjects = UnpackElements(selectionElements);

// Step 2: pack curtain wall elements, once we know the full extent of our flattened item list.
// The behaviour we're looking for:
// If parent wall is part of selection, does not select individual elements out. Otherwise, selects individual elements (Panels, Mullions) as atomic objects.
return PackCurtainWallElements(atomicObjects);
}

private List<Element> UnpackElements(IEnumerable<Element> elements)
{
var unpackedElements = new List<Element>(); // note: could be a hashset/map so we prevent duplicates (?)

foreach (var element in elements)
{
// UNPACK: Groups
if (element is Group g)
{
// POC: this might screw up generating hosting rel generation here, because nested families in groups get flattened out by GetMemberIds().
var groupElements = g.GetMemberIds().Select(_contextStack.Current.Document.GetElement);
unpackedElements.AddRange(UnpackElements(groupElements));
}
// UNPACK: Family instances (as they potentially have nested families inside)
else if (element is FamilyInstance familyInstance)
{
var familyElements = familyInstance
.GetSubComponentIds()
.Select(_contextStack.Current.Document.GetElement)
.ToArray();

if (familyElements.Length != 0)
{
unpackedElements.AddRange(UnpackElements(familyElements));
}

unpackedElements.Add(familyInstance);
}
else
{
unpackedElements.Add(element);
}
}
// Why filtering for duplicates? Well, well, well... it's related to the comment above on groups: if a group
// contains a nested family, GetMemberIds() will return... duplicates of the exploded family components.
return unpackedElements.GroupBy(el => el.Id).Select(g => g.First()).ToList(); // no disinctBy in here sadly.
}

private List<Element> PackCurtainWallElements(List<Element> elements)
{
var ids = elements.Select(el => el.Id).ToArray();
elements.RemoveAll(element =>
(element is Mullion m && ids.Contains(m.Host.Id)) || (element is Panel p && ids.Contains(p.Host.Id))
);
return elements;
}

/// <summary>
/// Given a set of atomic elements, it will return a list of all their ids as well as their subelements. This currently handles <b>curtain walls</b> and <b>stacked walls</b>.
/// This might not be an exhaustive list of valid objects with "subelements" in revit, and will need revisiting.
/// </summary>
/// <param name="elements"></param>
/// <returns></returns>
public List<string> GetElementsAndSubelementIdsFromAtomicObjects(List<Element> elements)
{
var ids = new HashSet<string>();
foreach (var element in elements)
{
switch (element)
{
case Wall wall:
if (wall.CurtainGrid is { } grid)
{
foreach (var mullionId in grid.GetMullionIds())
{
ids.Add(mullionId.ToString());
}
foreach (var panelId in grid.GetPanelIds())
{
ids.Add(panelId.ToString());
}
}
else if (wall.IsStackedWall)
{
foreach (var stackedWallId in wall.GetStackedWallMemberIds())
{
ids.Add(stackedWallId.ToString());
}
}
break;
default:
break;
}

ids.Add(element.Id.ToString());
}

return ids.ToList();
}
}

This file was deleted.

Loading
Loading