Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

D ui3 123 receiving non native attributes revit params gh attributes etc #3526

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
c97beff
collect attributes as strings (Brep randomly fails)
KatKatKateryna Jun 18, 2024
83c5629
pass function to get object attributes
KatKatKateryna Jun 18, 2024
dba5359
update passed function; add traversal for Revit parameter wrapper Base
KatKatKateryna Jun 18, 2024
db54c3b
remove stackOverflowException
KatKatKateryna Jun 19, 2024
3d2cccc
fix field path functions
KatKatKateryna Jun 19, 2024
ffc70e5
don't add already existing layers
KatKatKateryna Jun 19, 2024
4f624c5
add Rhino userStrings; get only dynamic values from Objects.Geometry;…
KatKatKateryna Jun 19, 2024
46314c3
some restructuring
KatKatKateryna Jun 19, 2024
a2fe04b
speed up check for existing datasets
KatKatKateryna Jun 19, 2024
6e05b94
only use "parameters" Base obj, if the elements are actually RevitPar…
KatKatKateryna Jun 19, 2024
8af6f60
only search for existing Tables if Feature Class wasn't already deleted
KatKatKateryna Jun 19, 2024
60e4950
get FieldType from actual values, default to String if needed
KatKatKateryna Jun 19, 2024
a6381bd
cast bool to string
KatKatKateryna Jun 19, 2024
479e79d
Merge branch 'dui3/alpha' into DUI3-123-Receiving-Non-Native-Attribut…
KatKatKateryna Jun 19, 2024
0773032
Merge branch 'dui3/alpha' into DUI3-123-Receiving-Non-Native-Attribut…
KatKatKateryna Jun 20, 2024
9754ac5
receive only Typed primitive properties from non-native apps
KatKatKateryna Jun 20, 2024
37f6b9a
typo
KatKatKateryna Jun 20, 2024
c6a1031
skip the properties for now
KatKatKateryna Jun 20, 2024
6add5dd
Revert "skip the properties for now"
KatKatKateryna Jun 20, 2024
9a33f40
Revert "typo"
KatKatKateryna Jun 20, 2024
46caeaf
Revert "receive only Typed primitive properties from non-native apps"
KatKatKateryna Jun 20, 2024
531551a
cast to string
KatKatKateryna Jun 20, 2024
77440ab
option for dealing with nullable types (#3528)
adamhathcock Jun 20, 2024
cce9a77
remove unnecessary null check
KatKatKateryna Jun 20, 2024
3cdca42
default to null, if cast to string fails
KatKatKateryna Jun 20, 2024
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 @@ -135,6 +135,9 @@ CancellationToken cancellationToken
// add layer URI to bakedIds
bakedObjectIds.Add(trackerItem.MappedLayerURI == null ? "" : trackerItem.MappedLayerURI);

// mark dataset as already created
bakedMapMembers[trackerItem.DatasetId] = mapMember;

// add report item
AddResultsFromTracker(trackerItem, results);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System.Collections;
using ArcGIS.Core.Data;
using ArcGIS.Core.Data.Exceptions;
using Objects.GIS;
using Speckle.Core.Logging;
using Speckle.Core.Models;
using FieldDescription = ArcGIS.Core.Data.DDL.FieldDescription;

Expand All @@ -16,38 +18,39 @@ public ArcGISFieldUtils(ICharacterCleaner characterCleaner)
_characterCleaner = characterCleaner;
}

public RowBuffer AssignFieldValuesToRow(RowBuffer rowBuffer, List<FieldDescription> fields, GisFeature feat)
public RowBuffer AssignFieldValuesToRow(
RowBuffer rowBuffer,
List<FieldDescription> fields,
Dictionary<string, object?> attributes
)
{
foreach (FieldDescription field in fields)
{
// try to assign values to writeable fields
if (feat.attributes is not null)
if (attributes is not null)
{
string key = field.AliasName; // use Alias, as Name is simplified to alphanumeric
FieldType fieldType = field.FieldType;
var value = feat.attributes[key];
if (value is not null)
var value = attributes[key];

try
{
// POC: get all values in a correct format
try
{
rowBuffer[key] = GISAttributeFieldType.SpeckleValueToNativeFieldType(fieldType, value);
}
catch (GeodatabaseFeatureException)
{
//'The value type is incompatible.'
// log error!
rowBuffer[key] = null;
}
catch (GeodatabaseFieldException)
{
// non-editable Field, do nothing
}
rowBuffer[key] = GISAttributeFieldType.SpeckleValueToNativeFieldType(fieldType, value);
}
else
catch (GeodatabaseFeatureException)
{
//'The value type is incompatible.'
// log error!
rowBuffer[key] = null;
}
catch (GeodatabaseFieldException)
{
// non-editable Field, do nothing
}
catch (GeodatabaseGeneralException)
{
// The index passed was not within the valid range. // unclear reason of the error
}
}
}
return rowBuffer;
Expand Down Expand Up @@ -88,4 +91,186 @@ public List<FieldDescription> GetFieldsFromSpeckleLayer(VectorLayer target)
}
return fields;
}

public List<(FieldDescription, Func<Base, object?>)> CreateFieldsFromListOfBase(List<Base> target)
{
List<(FieldDescription, Func<Base, object?>)> fieldsAndFunctions = new();
List<string> fieldAdded = new();

foreach (var baseObj in target)
{
// get all members by default, but only Dynamic ones from the basic geometry
Dictionary<string, object?> members = new();

// leave out until we decide which properties to support on Receive
/*
if (baseObj.speckle_type.StartsWith("Objects.Geometry"))
{
members = baseObj.GetMembers(DynamicBaseMemberType.Dynamic);
}
else
{
members = baseObj.GetMembers(DynamicBaseMemberType.All);
}
*/

foreach (KeyValuePair<string, object?> field in members)
{
// POC: TODO check for the forbidden characters/combinations: https://support.esri.com/en-us/knowledge-base/what-characters-should-not-be-used-in-arcgis-for-field--000005588
Func<Base, object?> function = x => x[field.Key];
TraverseAttributes(field, function, fieldsAndFunctions, fieldAdded);
}
}

// change all FieldType.Blob to String
// "Blob" will never be used on receive, so it is a placeholder for non-properly identified fields
for (int i = 0; i < fieldsAndFunctions.Count; i++)
{
(FieldDescription description, Func<Base, object?> function) = fieldsAndFunctions[i];
if (description.FieldType is FieldType.Blob)
{
fieldsAndFunctions[i] = new(
new FieldDescription(description.Name, FieldType.String) { AliasName = description.AliasName },
function
);
}
}

return fieldsAndFunctions;
}

private void TraverseAttributes(
KeyValuePair<string, object?> field,
Func<Base, object?> function,
List<(FieldDescription, Func<Base, object?>)> fieldsAndFunctions,
List<string> fieldAdded
)
{
if (field.Value is Base attributeBase)
{
// only traverse Base if it's Rhino userStrings, or Revit parameter, or Base containing Revit parameters
if (field.Key == "parameters")
{
foreach (KeyValuePair<string, object?> attributField in attributeBase.GetMembers(DynamicBaseMemberType.Dynamic))
{
// only iterate through elements if they are actually Revit Parameters or parameter IDs
if (
attributField.Value is Objects.BuiltElements.Revit.Parameter
|| attributField.Key == "applicationId"
|| attributField.Key == "id"
)
{
KeyValuePair<string, object?> newAttributField =
new($"{field.Key}.{attributField.Key}", attributField.Value);
Func<Base, object?> functionAdded = x => (function(x) as Base)?[attributField.Key];
TraverseAttributes(newAttributField, functionAdded, fieldsAndFunctions, fieldAdded);
}
}
}
else if (field.Key == "userStrings")
{
foreach (KeyValuePair<string, object?> attributField in attributeBase.GetMembers(DynamicBaseMemberType.Dynamic))
{
KeyValuePair<string, object?> newAttributField = new($"{field.Key}.{attributField.Key}", attributField.Value);
Func<Base, object?> functionAdded = x => (function(x) as Base)?[attributField.Key];
TraverseAttributes(newAttributField, functionAdded, fieldsAndFunctions, fieldAdded);
}
}
else if (field.Value is Objects.BuiltElements.Revit.Parameter)
{
foreach (
KeyValuePair<string, object?> attributField in attributeBase.GetMembers(DynamicBaseMemberType.Instance)
)
{
KeyValuePair<string, object?> newAttributField = new($"{field.Key}.{attributField.Key}", attributField.Value);
Func<Base, object?> functionAdded = x => (function(x) as Base)?[attributField.Key];
TraverseAttributes(newAttributField, functionAdded, fieldsAndFunctions, fieldAdded);
}
}
else
{
// for now, ignore all other properties of Base type
}
}
else if (field.Value is IList attributeList)
{
int count = 0;
foreach (var attributField in attributeList)
{
KeyValuePair<string, object?> newAttributField = new($"{field.Key}[{count}]", attributField);
Func<Base, object?> functionAdded = x => (function(x) as List<object?>)?[count];
TraverseAttributes(newAttributField, functionAdded, fieldsAndFunctions, fieldAdded);
count += 1;
}
}
else
{
TryAddField(field, function, fieldsAndFunctions, fieldAdded);
}
}

private void TryAddField(
KeyValuePair<string, object?> field,
Func<Base, object?> function,
List<(FieldDescription, Func<Base, object?>)> fieldsAndFunctions,
List<string> fieldAdded
)
{
try
{
string key = field.Key;
string cleanKey = _characterCleaner.CleanCharacters(key);

if (cleanKey == FID_FIELD_NAME) // we cannot add field with reserved name
{
return;
}

if (!fieldAdded.Contains(cleanKey))
{
// use field.Value to define FieldType
FieldType fieldType = GISAttributeFieldType.GetFieldTypeFromRawValue(field.Value);

FieldDescription fieldDescription = new(cleanKey, fieldType) { AliasName = key };
fieldsAndFunctions.Add((fieldDescription, function));
fieldAdded.Add(cleanKey);
}
else
{
// if field exists, check field.Value again, and revise FieldType if needed
int index = fieldsAndFunctions.TakeWhile(x => x.Item1.Name != cleanKey).Count();

(FieldDescription, Func<Base, object?>) itemInList;
try
{
itemInList = fieldsAndFunctions[index];
}
catch (Exception ex) when (!ex.IsFatal())
{
return;
}

FieldType existingFieldType = itemInList.Item1.FieldType;
FieldType newFieldType = GISAttributeFieldType.GetFieldTypeFromRawValue(field.Value);

// adjust FieldType if needed, default everything to Strings if fields types differ:
// 1. change to NewType, if old type was undefined ("Blob")
// 2. change to NewType if it's String (and the old one is not)
if (
newFieldType != FieldType.Blob && existingFieldType == FieldType.Blob
|| (newFieldType == FieldType.String && existingFieldType != FieldType.String)
)
{
fieldsAndFunctions[index] = (
new FieldDescription(itemInList.Item1.Name, newFieldType) { AliasName = itemInList.Item1.AliasName },
itemInList.Item2
);
}
}
}
catch (GeodatabaseFieldException)
{
// do nothing
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,15 @@ public void AddFeaturesToTable(Table newFeatureClass, List<GisFeature> gisFeatur
{
using (RowBuffer rowBuffer = newFeatureClass.CreateRowBuffer())
{
newFeatureClass.CreateRow(_fieldsUtils.AssignFieldValuesToRow(rowBuffer, fields, feat)).Dispose();
newFeatureClass
.CreateRow(
_fieldsUtils.AssignFieldValuesToRow(
rowBuffer,
fields,
feat.attributes.GetMembers(DynamicBaseMemberType.Dynamic)
)
)
.Dispose();
}
}
}
Expand Down Expand Up @@ -50,34 +58,55 @@ public void AddFeaturesToFeatureClass(
}

// get attributes
newFeatureClass.CreateRow(_fieldsUtils.AssignFieldValuesToRow(rowBuffer, fields, feat)).Dispose();
newFeatureClass
.CreateRow(
_fieldsUtils.AssignFieldValuesToRow(
rowBuffer,
fields,
feat.attributes.GetMembers(DynamicBaseMemberType.Dynamic)
)
)
.Dispose();
}
}
}

public void AddNonGISFeaturesToFeatureClass(
FeatureClass newFeatureClass,
List<ACG.Geometry> features,
List<FieldDescription> fields
List<(Base baseObj, ACG.Geometry convertedGeom)> featuresTuples,
List<(FieldDescription, Func<Base, object?>)> fieldsAndFunctions
)
{
foreach (ACG.Geometry geom in features)
foreach ((Base baseObj, ACG.Geometry geom) in featuresTuples)
{
using (RowBuffer rowBuffer = newFeatureClass.CreateRowBuffer())
{
ACG.Geometry newGeom = geom;

// exception for Points: turn into MultiPoint layer
if (geom is ACG.MapPoint pointGeom)
{
newGeom = new ACG.MultipointBuilderEx(
new List<ACG.MapPoint>() { pointGeom },
ACG.AttributeFlags.HasZ
).ToGeometry();
}

rowBuffer[newFeatureClass.GetDefinition().GetShapeField()] = newGeom;

// TODO: get attributes
// newFeatureClass.CreateRow(_fieldsUtils.AssignFieldValuesToRow(rowBuffer, fields, feat)).Dispose();
newFeatureClass.CreateRow(rowBuffer).Dispose();
// set and pass attributes
Dictionary<string, object?> attributes = new();
foreach ((FieldDescription field, Func<Base, object?> function) in fieldsAndFunctions)
{
string key = field.AliasName;
attributes[key] = function(baseObj);
}
// newFeatureClass.CreateRow(rowBuffer).Dispose(); // without extra attributes
newFeatureClass
.CreateRow(
_fieldsUtils.AssignFieldValuesToRow(rowBuffer, fieldsAndFunctions.Select(x => x.Item1).ToList(), attributes)
)
.Dispose();
}
}
}
Expand Down
Loading
Loading