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

Some IXbimSolid elements are getting wrong results when performing boolean operations #458

Open
ar98nau opened this issue Dec 14, 2023 · 10 comments
Assignees

Comments

@ar98nau
Copy link

ar98nau commented Dec 14, 2023

Hi,

I am working with solid elements from an .ifc model that contains them modelled as IfcBuildingElementProxy elements. With the intention of detecting some positional cases I am using Xbim.Common.Geometry for boolean operations: Intersection(), Union() and Cut().

The case is that sometimes there are solid figures that behave as if they don't exist when performing boolean operations causing that operations to give wrong results, as if there was nothing to operate the other solid with.

I've looked into the initial model using XbimXplorer and the problematic elements appear perfectly. Also I tried generating a new IfcStore model with the element alone and store it to an .ifc file, and when opened with XbimXplorer the element is visible and exists with no problem.

If it helps, the initial .ifc file is the result of exporting a project in Allplan.

This is the original model:
image
And this is the result of an intersection done between the rectangular solid, which contains every other solid element, and each contained element:
image

@andyward
Copy link
Member

Can't really advise without seeing an example. Can you supply some example code and this test model?

@andyward andyward added the needs repro Needs reproduction steps - can't reproduce label Dec 14, 2023
@ar98nau
Copy link
Author

ar98nau commented Dec 18, 2023

Hi @andyward, thank you for giving this issue a try.
Here you have the IFC file of the screenshots above, hope it helps.
EN-04.zip.

To put into context I may explain the scope of the project. The initial IFC model has all solids spread in the space in a random position from the origin, I need to move those solids to a desired position and make some other transformations in their axis, all while maintaining the relative position between solids. Once I have this, I need the collisions between all solids with the name CAVITAT and those with the name XPS since I want those parts that are in touch with XPS solids. To do so without altering the original model I generate several new IfcStores along the way and put inside the modifications and transformations done to the elements. It's here when, after some translations and although being visible and identical to the original solid (only transformed) in the new IfcStore model, the Boolean operations start to fail for some random solids.

Here is an example of how I transform my solids:

var solidSet = geomEngine.CreateSolidSet();
solidSet.Add(solid);
var newTransform = new XbimMatrix3D();
newTransform.RotateAroundYAxis((180.0 * Math.PI) / 180.0);
var transformFlip = (IXbimSolidSet)solidSet.Transform(newTransform);

Here is how I create an object into a new IfcStore from the solid obtained after doing some transformations:

private static void CreateIfcGeometriesFromSolids(IfcStore model, IfcSpatialStructureElement parent, IXbimSolid shapeGeometry, IfcBuildingElement element, CustomXYZ position, string materialName, bool toMillimetres)
{
    double milli = toMillimetres ? 1000 : 1;

    var ifcFacetedBrep = model.Instances.New<IfcFacetedBrep>();
    var shell = model.Instances.New<IfcClosedShell>();

    foreach (var face in shapeGeometry.Faces)
    {
        var ifcFace = model.Instances.New<IfcFace>();
        var outerbound = model.Instances.New<IfcFaceOuterBound>();
        var polyloop = model.Instances.New<IfcPolyLoop>();

        List<IXbimVertex> vertices = face.OuterBound.Edges.SelectMany(e =>
        {
            List<IXbimVertex> verticesE = new List<IXbimVertex>
                    {
                        e.EdgeStart,
                        e.EdgeEnd
                    };
            return verticesE;
        }).ToList();
        vertices = vertices.Distinct().ToList();

        foreach (IXbimVertex vertice in vertices)
        {

            double x = vertice.VertexGeometry.X * milli;
            double y = vertice.VertexGeometry.Y * milli;
            double z = vertice.VertexGeometry.Z * milli;

            var cartesianPoint = model.Instances.New<IfcCartesianPoint>();
            cartesianPoint.SetXYZ(x, y, z);
            polyloop.Polygon.Add(cartesianPoint);
        }

        outerbound.Bound = polyloop;
        ifcFace.Bounds.Add(outerbound);
        shell.CfsFaces.Add(ifcFace);
    }

    CreateStructure(model, element, ifcFacetedBrep, shell);

    AddMaterial(model, element, materialName);

    SetPosition(position * milli, model, parent, element);
}

private static void CreateStructure(IfcStore model, IfcBuildingElement element, IfcFacetedBrep ifcFacetedBrep, IfcClosedShell shell)
{
	ifcFacetedBrep.Outer = shell;
	var shapeRepresentation = model.Instances.New<IfcShapeRepresentation>();
	var modelContext = model.Instances.OfType<IfcGeometricRepresentationContext>().FirstOrDefault();
	shapeRepresentation.ContextOfItems = modelContext;
	shapeRepresentation.RepresentationType = "Brep";
	shapeRepresentation.RepresentationIdentifier = "Body";
	shapeRepresentation.Items.Add(ifcFacetedBrep);
	var productDefinitionShape = model.Instances.New<IfcProductDefinitionShape>();
	productDefinitionShape.Representations.Add(shapeRepresentation);
	element.Representation = productDefinitionShape;
}

private static void AddMaterial(IfcStore model, IfcBuildingElement element, string materialName)
{
	var material = model.Instances.New<IfcMaterial>();
	material.Name = materialName;
	var ifcRelAssociatesMaterial = model.Instances.New<IfcRelAssociatesMaterial>();
	ifcRelAssociatesMaterial.RelatingMaterial = material;
	ifcRelAssociatesMaterial.RelatedObjects.Add(element);
}

private static void SetPosition(CustomXYZ position, IfcStore model, IfcSpatialStructureElement parent, IfcBuildingElement element)
{
	var origin = model.Instances.New<IfcCartesianPoint>();
	origin.SetXYZ(position.x, position.y, position.z);
	var lp = model.Instances.New<IfcLocalPlacement>();
	var ax3D = model.Instances.New<IfcAxis2Placement3D>();
	ax3D.Location = origin;
	lp.RelativePlacement = ax3D;
	lp.PlacementRelTo = parent.ObjectPlacement;
	element.ObjectPlacement = lp;
}

This is the fragment of my code where I have issues after performing the Intersect boolean operation:

List<IfcBuildingElementProxy> XPSs = ifcModel.Instances.OfType<IfcBuildingElementProxy>().Where(i => ((IfcMaterial)i.Material).Name.Value.ToString().Contains(XPS_OBJECT_ID) && i.Representation != null).ToList();
List<IfcBuildingElementProxy> fresades = ifcModel.Instances.OfType<IfcBuildingElementProxy>().Where(i => ((IfcMaterial)i.Material).Name.Value.ToString().Contains(CAVITAT_OBJECT_ID) && i.Representation != null).ToList();

IXbimSolidSet xpsSolids = null;
foreach (IfcBuildingElementProxy xps in XPSs)
{
    var xpsBrep = xps.Representation.Representations.SelectMany(r => r.Items.OfType<IfcFacetedBrep>()).FirstOrDefault();
    var xpsGeom = geomEngine.CreateSolidSet(xpsBrep, _logger);
    XbimMatrix3D matrixXPS = geomEngine.ToMatrix3D(xps.ObjectPlacement, _logger);
    var xpsGeomTransform = (IXbimSolidSet)xpsGeom.Transform(matrixXPS);

    if (xpsSolids == null) xpsSolids = xpsGeomTransform;
    else xpsSolids.Add(xpsGeomTransform);
}

IXbimSolidSet resultsIntersect = null;
foreach (IfcBuildingElementProxy fresada in fresades)
{
    var fresadaBrep = fresada.Representation.Representations.SelectMany(r => r.Items.OfType<IfcFacetedBrep>()).FirstOrDefault();
    var fresadaGeom = geomEngine.CreateSolidSet(fresadaBrep, _logger);
    XbimMatrix3D matrixF = geomEngine.ToMatrix3D(fresada.ObjectPlacement, _logger);
    var fresadaGeomTransform = (IXbimSolidSet)fresadaGeom.Transform(matrixF);

    var fresadaIntersect = xpsSolids.Intersection(fresadaGeomTransform, 0, _logger);

    if (fresadaIntersect.Count > 0)
    {
        if (resultsIntersect == null)
            resultsIntersect = fresadaIntersect;
        else
            resultsIntersect.Add(fresadaIntersect);
    }
}

Also I've recently tried performing the intersect boolean operation between CAVITAT solids and XPS solids directly to the initial IFC model (without any transformation) and the result is slightly different, in this case, I only lose the two cylinders. So the behaviour is driving me mad since it seems totally random to me.
image

@andyward andyward self-assigned this Dec 18, 2023
@andyward andyward removed the needs repro Needs reproduction steps - can't reproduce label Dec 18, 2023
@ar98nau
Copy link
Author

ar98nau commented Jan 18, 2024

Hi @andyward ,
I was hoping to have some news about this issue. If there's something I can do or the information provided isn't enough just tell me.

Thanks in advance.

@andyward
Copy link
Member

Sorry, this is too hard to diagnose from the above. It needs a solution someone can debug. I'd create a minimal working project demonstrating the issue. I.e. in this case select two elements that you expect to intersect but maybe don't. Perhaps build up some unit tests to check your assumptions?

@ar98nau ar98nau closed this as completed Sep 24, 2024
@ar98nau ar98nau closed this as not planned Won't fix, can't repro, duplicate, stale Sep 24, 2024
@ar98nau
Copy link
Author

ar98nau commented Sep 27, 2024

Hi @andyward ,

I've decided to reopen this issue following the thread opened several months ago, since the problem is still present.
However, I've been able to further analyse the problem and now I can present the issue properly.

First, I would like to correct a mistake I made: the objects are not disappearing but the boolean operation is not being done properly for those objects. This wrong construction of the intersect caused some triggers to arise and hide the intersection from the final IFC model.

The real problem is that for some objects the Intersection() function is not doing the intersection, instead, it does something like a cut or a hollow out. This behaviour can be found in intersecting objects, but also in objects where the intersection must be null.

1
2

This happens only for some objects, not all objects of the same model, however, there seems to be no difference between the ones that intersect well from those that have this error. Also, all intersections have an object in common which is the big Cuboid.

3

I have attached an example IFC file and a DEMO source code. The DEMO will generate a folder in the IFC's directory classifying which objects have errors in the intersection and which intersect correctly.

IFC_ERROR.zip

Don't hesitate to contact me in case something goes wrong.

I really appreciate any help you can provide.

@ar98nau ar98nau reopened this Sep 27, 2024
@andyward
Copy link
Member

Hi @ar98nau

I took a quick look

I think you need to distill this down to a much simpler example to get help. i.e you should be able to demo this with less than 20 lines of code, not 600+ in ProcessIfc4.cs. I'm, not sure which of the elements you're intersecting is causing an issue for you.

If you can just say element 2832 and element XYZ fail to intersect we'll have something to go at. As it is this test is a bit confusing - especially since it produces false errors since it's possible to intersect and still have the same bounding box. i.e this logic is wrong:

// Line 84 of ProcessIfc4.cs
if (Math.Round(fresadaIntersect.BoundingBox.SizeX, 1) != Math.Round(xpsSolids.BoundingBox.SizeX, 1) ||
    Math.Round(fresadaIntersect.BoundingBox.SizeY, 1) != Math.Round(xpsSolids.BoundingBox.SizeY, 1) ||
    Math.Round(fresadaIntersect.BoundingBox.SizeZ, 1) != Math.Round(xpsSolids.BoundingBox.SizeZ, 1))

For instance the intersection between 'xps' 2832 and 'fresada' 1579 gives:
image

... which you mark as error but you can see is successful.

BTW: Download this CAD Assistant tool to visualise the OpenCascade generated Breps: https://www.opencascade.com/products/cad-assistant/

image

If you come back with a simpler test case we can try to help

@ar98nau
Copy link
Author

ar98nau commented Sep 30, 2024

Hello @andyward,

I apologise for not giving a DEMO that helps figure out what's happening with the intersection. I'll do my best to simplify the code, although some steps are previously necessary for the intersection.

In the meantime, I'd like to discuss some things with you.
First of all, regarding the condition to detect an error, it is true that any intersection between this 'xps' cuboid and another cuboid of the same dimensions will generate a false error, however, for the requirements and conditions held in the project, this scenario is impossible to happen, thus this condition is just a way to identify results that are not meant to happen.

This being said, the second topic is regarding the intersection you took as an example. If this case is the same one I sent you or at least it was taken from my IFC, the intersection (all of them) should not result in a bounding box as large as the 'xps' cuboid, first because is impossible that such 'fresada' exists, and then because that is not really the intersection between the 'xps' and the 'fresada'. So, I don't understand why you say this intersection is successful, maybe I'm misunderstanding some concept regarding the intersection between 3D Solids or I've not been clear when explaining the casuistic. I'll like to know your point of view, maybe we are both right and the problem lies in the construction of the original IFC model.

In any case, I attach this example to enforce the explanation of this use case.
1

@andyward
Copy link
Member

andyward commented Oct 1, 2024

Yes, let me know when you have simplified. I really just want something we can unit test.
Yes it could be an issue in the IFC model. My guess is some precision issue in the model is causing the OCC intersection to fail. What you're seeing is just a subtraction (Cut). I'll see if anyone else can help as geometry is not my particular area

@ar98nau ar98nau changed the title Some IXbimSolid elements don't exist when performing boolean operations Some IXbimSolid elements are getting wrong results when performing boolean operations Oct 7, 2024
@ar98nau
Copy link
Author

ar98nau commented Oct 8, 2024

Hello @andyward,

First of all, thanks for keeping this issue alive despite the problem's difficulty and not being in your field, I would be grateful if you could reach someone who can give us a hand.

I have attached a simplified version of the DEMO. I reduced as much code as I could, ProcessIfc4.cs is no longer +600 lines, however, some code that may seem dummy is still necessary to perform intersections or to give clear results.
ifc_error_DEMO.zip

Regarding the cause of this issue, I also don't discard the possibility that our IFC model has some characteristics that don't match with your library, although being exported from a BIM program (Allplan) that states to export in standard IFC4 format.
Taking this into account, I'd like to introduce a problem we found last week with such IFC models that may help find the issue between these models and your library. In this case, the intersection is not getting a Cut instead of an intersection but when intersecting it returns a corrupted Solid3D with malformed faces:
4
(Notice that when executing this IFC with the zipped program, none of the errors are detected as such so they would be found in the "OK" folder mixed with correct results) IFC_ERROR_2.zip

Don't hesitate to contact me in case you need something else.

@ar98nau
Copy link
Author

ar98nau commented Oct 28, 2024

Hello @andyward,
Is there any news on this issue?
Thanks in advance.

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