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

[Question] How to get all referenced instances for a particular instance? #384

Closed
youshengCode opened this issue Jun 10, 2021 · 4 comments
Closed

Comments

@youshengCode
Copy link
Contributor

There is a way to get a list of all referenced instances which have used the current entity?

Take an IfcCartesianPoint for example, there is a way to get all entities that have used this point?

Assemblies and versions affected:

Using the latest stable build, Xbim.Essentials 5.1.323

Steps (or code) to reproduce the issue:

For this piece of IFC, placements #11, #14 have used point #7.

Just start with #7, could I retrieve a list with #11, #14 and others, if there are more entities that have referenced this point?

#7=IFCCARTESIANPOINT((0.,0.,0.));
#8=IFCDIRECTION((1.,0.,0.));
#9=IFCDIRECTION((0.,1.,0.));
#10=IFCDIRECTION((0.,0.,1.));
#11=IFCAXIS2PLACEMENT3D(#7,#10,#8);
#12=IFCAXIS2PLACEMENT2D(#13,$);
#13=IFCCARTESIANPOINT((0.,0.));
#14=IFCAXIS2PLACEMENT3D(#7,#10,#8);

Minimal file to reproduce the issue:

This piece of IFC comes from the buildingSMART sample test files.

[Sample-Test-Files/cube-advanced-brep.ifc at master · buildingSMART/Sample-Test-Files (github.com)](https://github.com/buildingSMART/Sample-Test-Files/blob/master/IFC 4.0/BuildingSMARTSpec/cube-advanced-brep.ifc)

Expected behavior:

Get a list of instance or entity labels, who referenced or have used this instance.

Additional Details

I found a similar method Traverse() in the IfcOpenShell, basically I'm trying to accomplish the same thing.

IfcOpenShell/file.py at fc05bdf6d6465251fec8e279493d1bf5f3017617 · IfcOpenShell/IfcOpenShell (github.com)

@martin1cerny
Copy link
Member

Yes, this is possible. Have a look at the code we use to delete an entity. That also needs to inspect the model and find all relating entities to keep the model consistent.

@youshengCode
Copy link
Contributor Author

Thanks for your quick response and your advice, Martin,

That's exactly what I'm looking for.

But seems it doesn't take care all referring types.

I have used the same method for GetReferingTypes, and ReplaceReferences in ModelHelper.cs. In the ReplaceReferences, I just commented out those lines for replacing the value.

using (var model = IfcStore.Open(fileName))
{
    var entities = model.Instances.OfType<IIfcCartesianPoint>();
    var uniqueTypes = new HashSet<Type>(entities.Select(e => e.GetType()));
    var referingTypes = new HashSet<ReferingType>(uniqueTypes.SelectMany(t => GetReferingTypes(model, t)));
    foreach (var referingType in referingTypes)
        ReplaceReferences<IPersistEntity, IPersistEntity>(model, entities, referingType, null);
}

For example, I can't find #123 has been used in #122. (this piece is from the same sample file in the description).

#122=IFCBSPLINESURFACEWITHKNOTS(3,1,((#123,#124),(#125,#126),(#127,#128),(#129,#130)),.UNSPECIFIED.,.F.,.F.,.U.,(4,4),(2,2),(0.,1224.74487139159),(3.,4.),.UNSPECIFIED.);
#123=IFCCARTESIANPOINT((-0.5,0.5,0.));
#124=IFCCARTESIANPOINT((-0.5,-0.5,0.));
#125=IFCCARTESIANPOINT((-0.561004233964073,0.27232909936926,0.333333333333333));
#126=IFCCARTESIANPOINT((-0.27232909936926,-0.561004233964073,0.333333333333333));
#127=IFCCARTESIANPOINT((-0.622008467928146,0.0446581987385206,0.666666666666667));
#128=IFCCARTESIANPOINT((-0.0446581987385206,-0.622008467928146,0.666666666666667));
#129=IFCCARTESIANPOINT((-0.683012701892219,-0.183012701892219,1.));
#130=IFCCARTESIANPOINT((0.183012701892219,-0.683012701892219,1.));

It seems like the referring types of IFCCARTESIANPOINT not taking IFCBSPLINESURFACEWITHKNOTS.

Did I miss something? Please let me know. Thanks a lot.

@martin1cerny
Copy link
Member

IFCBSPLINESURFACEWITHKNOTS is one two classes in IFC using nested (2D) lists. The code looking for references might be missing these. Feel free to contribute to the project with a pull request fixing this issue. All contributions are most welcome.

@youshengCode
Copy link
Contributor Author

I fixed this issue in the ModelHelper, it works like a charm. Thanks a lot, Martin.

I added a new prop in the struct ReferingType.

private struct ReferingType
{
    public ExpressType Type;
    public List<ExpressMetaProperty> SingleReferences;
    public List<ExpressMetaProperty> ListReferences;
    public List<ExpressMetaProperty> NestedListReferences;
}

And added the related part to get referring types.

private static IEnumerable<ReferingType> GetReferingTypes(IModel model, Type entityType)
{
    // ......
    
    foreach (var type in types)
    {
        if (!ReferingTypesCache.TryGetValue(type.Type, out ReferingType rt))
        {
            var singleReferences = type.Properties.Values.Where(p =>
                p.EntityAttribute != null && p.EntityAttribute.Order > 0 &&
                p.PropertyInfo.PropertyType.GetTypeInfo().IsAssignableFrom(entityType)).ToList();
            var listReferences = type.Properties.Values.Where(p =>
                p.EntityAttribute != null && p.EntityAttribute.Order > 0 &&
                p.PropertyInfo.PropertyType.GetTypeInfo().IsGenericType &&
                p.PropertyInfo.PropertyType.GenericTypeArgumentIsAssignableFrom(entityType)).ToList();
             //add nested list references
            var nestedListReferences = type.Properties.Values.Where(p =>
                p.EntityAttribute != null && p.EntityAttribute.Order > 0 &&
                p.PropertyInfo.PropertyType.GetTypeInfo().IsGenericType &&
                p.PropertyInfo.PropertyType.GetItemTypeFromGenericType().IsGenericType &&
                p.PropertyInfo.PropertyType.GetItemTypeFromGenericType().GenericTypeArgumentIsAssignableFrom(entityType)).ToList();
            if (!singleReferences.Any() && !listReferences.Any() && !nestedListReferences.Any())
                continue;

            rt = new ReferingType { Type = type, SingleReferences = singleReferences, ListReferences = listReferences, NestedListReferences = nestedListReferences };
            ReferingTypesCache.TryAdd(type.Type, rt);
        }
        referingTypes.Add(rt);
    }
    return referingTypes;
}

Then I used the ReplaceReferences (the one with the IEnumerable argument) to check and replace the entities.

private static void ReplaceReferences<TEntity, TReplacement>(IModel model, IEnumerable<TEntity> entities, ReferingType referingType, TReplacement replacement)
    where TEntity : IPersistEntity where TReplacement : TEntity
{
    // ......
        
    foreach (var toCheck in entitiesToCheck)
    {
        // ......

        //check for nested list references
        foreach (var pInfo in referingType.NestedListReferences.Select(p => p.PropertyInfo))
        {
            var pVal = pInfo.GetValue(toCheck);
            if (pVal == null) continue;

            //it might be uninitialized optional item set
            if (pVal is IOptionalItemSet optSet && !optSet.Initialized)
                continue;

            //or it is non-optional item set implementing IList
            if (!(pVal is IList nestedItemSet))
                throw new XbimException($"Unable to remove items from {referingType.Type.Name}.{pInfo.Name}. No IList implementation.");

            for (int i = 0; i < nestedItemSet.Count; i++)
            {
                if (!(nestedItemSet[i] is IList itemSet))
                    throw new XbimException($"Unable to remove items from {referingType.Type.Name}.{pInfo.Name}. No IList implementation.");

                for (int j = 0; j < itemSet.Count; j++)
                {
                    var item = itemSet[j];
                    if (!hash.Contains(item))
                        continue;
                    itemSet.RemoveAt(j);
                    if (replacement != null)
                        itemSet.Insert(j, replacement);
                    else
                        j--; // keep in sync
                }
            }
        }
    }

}

I have checked the documentation, there are IfcBSplineSurface and IfcBSplineSurfaceWithKnots using the nested list in the property ControlPointsList. I have tested this piece of code with those two classes. It works fine for me for now.

I will make a pull request later, but please notice that

I have only updated the GetReferingTypes(IModel model, Type entityType) and ReplaceReferences<TEntity, TReplacement>(IModel model, IEnumerable entities, ReferingType referingType, TReplacement replacement) these two methods mentioned above.

Since I am not very familiar with the whole project, this needs to be further adapted in other related methods.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants