XbimEssentials icon indicating copy to clipboard operation
XbimEssentials copied to clipboard

Add support for new creating newer schema entities to the EntityCreator

Open andyward opened this issue 11 months ago • 5 comments

Xbim.Ifc.EntityCreator is a cross schema factory class enabling the correctly typed entities for a model to be created without specifying the concrete schema implementation. Currently it only supports Entity types in the lowest common denominator (IFC2x3)

So it doesn't support new entities in IFC4x3 (and also IFC4). E.g you can't create and IfcSensor or IfcRoad using the EntityCreator - you have to create these concretely. Clearly constructing these in IFC2x3 would make no sense but it would be nice to create a Sensor agnostic of the IFC schema being IFC4 or IFC4x3

Options:

  1. We support the highest schema and throw when not supported
  2. We extend the contract to have IsSchemaSupported<TInterface>() and a Create<TInterface>(Action<T>)

andyward avatar Jan 30 '25 12:01 andyward

Hi,

while transforming our application to be schema-independent by using the EntityCreator following Objects are missing:

  • IfcMaterialProperties
  • IfcIndexedPolyCurve
  • IfcLightFixture
  • IfcTriangulatedFaceSet
  • IndexedPolygonalFace
  • PolygonalFaceSet
  • IfcCartesianPointList2D
  • IfcCartesianPointList3D

Is there a reason why those are not supported or are they from higher schemas?

WalchAndreas avatar Feb 13 '25 18:02 WalchAndreas

Yes these are all types introduced in IFC4 and as touched upon in #600, EntityCreator only supports IFC 2x3 entities as a baseline.

What I think may work is to have some extension to EntityCreator that introduces the same approach for IFC 4+.

Where it gets complicated is if you wanted to create an IFC2x3 equivalent to IfcLightFIxture - i.e. IfcFlowTerminal defined by a IfcLightFixtureType

andyward avatar Feb 13 '25 18:02 andyward

FYI IFC4 changelog here: https://standards.buildingsmart.org/IFC/DEV/IFC4_2/FINAL/HTML/link/ifc2x3-to-ifc4-changelog.htm

andyward avatar Feb 13 '25 18:02 andyward

Some example of how this could be implemented using ExtensionMethods on IModel so you can try out yourself.

  1. Compile time approach:

https://gist.github.com/andyward/6d6281b0794571ef2429096ed50a4317

IModel model = new IO.Memory.MemoryModel(new EntityFactoryIfc4());

IIfcLightFixture light = model.LightFixture();   // IFC4+
IIfcFlowTerminal light2 = model.LightFixtureWithBackwardCompatibility(); // IFC2x3+
  1. Run-time approach

https://gist.github.com/andyward/d94b852a0fcc5dd83e13e482a13d3f16

IModel model = new IO.Memory.MemoryModel(new EntityFactoryIfc4());
if (model.SchemaSupportsEntity<IIfcLightFixture>())
{
    IIfcLightFixture wall = model.Create<IIfcLightFixture>(f => f.PredefinedType = IfcLightFixtureTypeEnum.SECURITYLIGHTING);
}
else
{
    // Fall back manually to Flow Terminal etc
}

This second approach might permit us to register substitutions as per LightFixtureWithBackwardCompatibility() example

andyward avatar Feb 17 '25 11:02 andyward

Thank you for the examples!

I experimented with the "Compile Time Approach" and could cover my use-cases. To avoid down casts from the more general IIfcFlowTerminal for later IFC-schemes, we provide 2 versions. This is not ideal, but the postfix should help to distinguish it.

Here are some parts of our routines:

public static class EntityCreatorExtensions
{
    public static @IIfcLightFixture LightFixtureExt(this IModel model, Action<@IIfcLightFixture> init = null)
    {
        return model.SchemaVersion switch
        {
            XbimSchemaVersion.Ifc4 => model.Instances.New<Xbim.Ifc4.ElectricalDomain.IfcLightFixture>(init),
            XbimSchemaVersion.Ifc4x3 => model.Instances.New<Xbim.Ifc4x3.ElectricalDomain.IfcLightFixture>(init),
            _ => throw new NotSupportedException($"Type IfcLightFixture is not supported in schema {model.SchemaVersion}") // IfcLightFixture Not supported in 2x3
        };
    }

    public static @IIfcFlowTerminal LightFixtureExtIFC2x3(this IModel model, Xbim.Ifc2x3.ElectricalDomain.IfcLightFixtureTypeEnum? lightType = null)
    {
        return model.SchemaVersion switch
        {
            XbimSchemaVersion.Ifc2X3 =>
                // NOTE: Ifc2X3 requires a LightFixtureType to type FlowTerminal as Light

                // CAUTION - Instantiation from LightFixtureType: CreateAttachInstancedRepresentation links LightFixtureType to Flowterminal! (lightType == null)
                // CAUTION - Direct light creation: IFC2x3 requires an IfcLightFixtureTypeEnum! (lightType != null)
                model.Instances.New<Xbim.Ifc2x3.SharedBldgServiceElements.IfcFlowTerminal>(o => {
                    if (lightType != null)
                    {
                        o.AddDefiningType(model.Instances.New<Xbim.Ifc2x3.ElectricalDomain.IfcLightFixtureType>(lt => {
                            lt.PredefinedType = lightType.Value;
                            lt.Name = "LightType";
                        }));    
                    }
                }),
            XbimSchemaVersion.Ifc4 => model.Instances.New<Xbim.Ifc4.ElectricalDomain.IfcLightFixture>(),
            XbimSchemaVersion.Ifc4x3 => model.Instances.New<Xbim.Ifc4x3.ElectricalDomain.IfcLightFixture>(),
            _ => throw new NotSupportedException($"Type IfcLightFixture is not supported in schema {model.SchemaVersion}")
        };
    }
}

public static IIfcLightFixture CreateLightEmpty(this IModel model, string name, IIfcObjectPlacement placement, IIfcShapeRepresentation lightShape, IfcLightFixtureTypeEnum? lightType = null)
{
    // CAUTION only supports IFC4+
    return model.LightFixtureExt(t =>
    {
        if (lightType != null) t.PredefinedType = lightType.Value;
        t.Name = name;
        t.ObjectPlacement = placement;
        t.Representation = model.Factory().ProductDefinitionShape(r => r.Representations.Add(lightShape));
    });
}

public static IIfcFlowTerminal CreateLightEmptyIFC2x3(this IModel model, string name, IIfcObjectPlacement placement, IIfcShapeRepresentation lightShape, IfcLightFixtureTypeEnum? lightType = null)
{
    Xbim.Ifc2x3.ElectricalDomain.IfcLightFixtureTypeEnum? ifc2x3LightType = null;

    if (model.SchemaVersion == XbimSchemaVersion.Ifc2X3)
    {
        if (lightType == null)
        {
            ifc2x3LightType = Xbim.Ifc2x3.ElectricalDomain.IfcLightFixtureTypeEnum.NOTDEFINED;
        }
        else
        {
            ifc2x3LightType = lightType.Value switch
            {
                IfcLightFixtureTypeEnum.POINTSOURCE => Xbim.Ifc2x3.ElectricalDomain.IfcLightFixtureTypeEnum.POINTSOURCE,
                IfcLightFixtureTypeEnum.DIRECTIONSOURCE => Xbim.Ifc2x3.ElectricalDomain.IfcLightFixtureTypeEnum.DIRECTIONSOURCE,
                IfcLightFixtureTypeEnum.USERDEFINED => Xbim.Ifc2x3.ElectricalDomain.IfcLightFixtureTypeEnum.USERDEFINED,
                _ => Xbim.Ifc2x3.ElectricalDomain.IfcLightFixtureTypeEnum.NOTDEFINED
            };
        }
    }

    var t = model.LightFixtureExtIFC2x3(ifc2x3LightType);

    t.Name = name;
    t.ObjectPlacement = placement;
    t.Representation = model.Factory().ProductDefinitionShape(r => r.Representations.Add(lightShape));
    if (lightType != null && t is IIfcLightFixture lf) lf.PredefinedType = lightType.Value;

    return t;
}

WalchAndreas avatar Feb 27 '25 17:02 WalchAndreas