OSArch Community

Remove IfcSpaces with IfcOpenShell

  1. E

    I don't see any bug with ArchiCAD. What makes you think ArchiCAD did something wrong?

    Simply because IfcSpace (edited) isn't among the classes listed in the paranthesis above (IfcSite, IfcBuilding, IfcBuildingStorey). It's probably not wrong, but depends on how we choose to interpret the convention.

  2. M

    @Einar do you mean IfcSpace? If so, IfcSpace is indeed in the same "category" as IfcSite, IfcBuilding, etc. Those three are provided as examples. The struct rule lies in this sentence:

    it should be the same container element that is referenced by the IfcRelContainedInSpatialStructure containment relationship

  3. E

    You are right, I indeed meant IfcSpace.

    I'm curious if the same behaviour occurs for IFC4, and also curious why only the bottom storey dropped out but all other storeys remained correct.

    Me too! It would also be interesting to see if it was possible to fix it by replacing the IfcLocalPlacements, but maybe it would be easier to just ask Solibri.

  4. M

    @Einar I've linked this thread to someone I know who is closer to the Solibri guys and may be able to get a response, I'll update this thread if anything comes out of it.

  5. J

    @Einar regarding the transformations and without getting into the details of your implementation, are you sure you are rotating the objects with respect to the right center? It looks to me your are always rotating with respect to 0, 0, 0

  6. E

    @Jesusbill I'm not sure at all! I can try to explain how I think, and maybe you have an idea if it makes sense or not:

    As an example I have a wall that is positioned relative to a space in a certain coordinate system. The space is then positioned relative to a building storey. If both locations where in the same coordinate system it would just be a matter of adding the coordinates in each point togeter the get the walls placement relative to the building storey. That is not the case here because the points are given in two different coordinate systems, where the difference between them is the direction of the x-axis. My next step is to transform the point in the space to the coordinate system of the wall before they are added together. Isn't the coordinate systems origin the most logical point to rotate about?

  7. S

    No it is not the most logical,

    rotate about absolute (nested relative result) location of current node should be.

  8. J

    I get you, you have building storey -> space -> wall. And you say that the origins of these coordinate systems is the same? Are you sure about that?

    Isn't the coordinate systems origin the most logical point to rotate about?

    Yes

    My next step is to transform the point in the space to the coordinate system of the wall before they are added together

    This is not clear to me, if you want to remove the space you should calculate the wall placement with respect to the coordinate system of the building storey

  9. E

    I managed to solve a simplified version of the problem and verified it with a graphical solution:

    Still no luck with he ifc file. Probably best to give up at this point :D

  10. E

    -

  11. J

    I think you are not approaching it correctly. You want to end up with the coordinates of the wall point with respect to the coordinate system of the building storey, right? If yes then x should be negative and y positive but they should be oriented as the coordinatesystem of the building storey

    file:///home/jesusbill/Desktop/Screenshot%20from%202020-11-11%2018-50-11.png

  12. J

    here is a snippet I made to calculate the local coordinates of the wall wrt to the building storey. I am working with 3D coords as I find it easier, you do not need actually to calculate the cosines of anything, normalizing the reference directions give you the cosines (what called directional cosines).

    
    import numpy as np
    
    norm = np.linalg.norm
    
    z_norm = np.array([0, 0, 1]) # assuming z axis is always towards +Z global?
    
    refDir_ref = x_s = np.array([3, -1, 0]) # _s for space
    
    x_s = x_s / norm(x_s)
    
    y_s = np.cross(z_norm, x_s)
    
    R_s = np.array([x_s, y_s, z_norm]).transpose()
    
    location_ref = wall_loc = np.array([5000, 5000, 0])
    
    wall_glob = R_s.dot(wall_loc)
    
    print('global coordinate of wall wrt to space is = ', wall_glob)
    
    refDir_space = x_b = np.array([-1, 0, 0]) # _b for building storey
    
    x_b = x_b / norm(x_b)
    
    y_b = np.cross(z_norm, x_b)
    
    R_b = np.array([x_b, y_b, z_norm]).transpose()
    
    location_space = space_loc = np.array([1000, 4000, 0])
    
    space_glob = R_b.dot(space_loc)
    
    print('global coordinate of space wrt to building_storey is = ', space_glob)
    
    wall_tot_glob = space_glob + wall_glob
    
    print('global coordinate of wall wrt to building_storey is = ', wall_tot_glob)
    
    wall_tot_loc = R_b.transpose().dot(wall_tot_glob)
    
    print('local coordinate of wall wrt to building_storey is = ', wall_tot_loc)
    

    the output I get is

    
    global coordinate of wall wrt to space is =  [6324.55532034 3162.27766017    0.        ]
    
    global coordinate of space wrt to building_storey is =  [-1000. -4000.     0.]
    
    global coordinate of wall wrt to building_storey is =  [5324.55532034 -837.72233983    0.        ]
    
    local coordinate of wall wrt to building_storey is =  [-5324.55532034   837.72233983     0.        ]
    
  13. E

    Thanks for the code snippet!

    @Jesusbill said:

    You want to end up with the coordinates of the wall point with respect to the coordinate system of the building storey, right?

    I'm not sure what you mean by the coorindate system of the building storey, There is one IfcLocalPlacement object attached to the wall an one attached to the space. Each of them have their own x-axis (ref-dir). I am trying to replace the Location point in the IfcLocalPlacement which is connected to the wall.

  14. J

    Mmm maybe I rushed a bit, let me work a bit on this and I will get back to you. This part of transformations is very interesting for me as well.

    By wall point I referred I guess to the position of the wall's local placement. But what I did does not consider the change of orientation of the walls local placement when removing the space.

    In any case there are three local placements, right? From building storey, space and wall and you want to find an updated local placement of the wall when removing the local placement of the space from the equation. I will work on this

  15. E

    I "implemented" your code and tried to transform everything into global coordinates and set the refDir in the remaining IfcLocalPlacement to [1,0,0), but still not pretty in Solibri.

    
    # Edit placements and delete the ones that places spaces
    
    local_placements = f.by_type('IfcLocalPlacement')
    
    for lp in local_placements:
    
        if lp.PlacesObject[0].is_a('IfcSpace'):
    
            lp_space = lp
    
            location_s = lp_space.RelativePlacement.Location.Coordinates
    
            refDir_s = lp_space.RelativePlacement.RefDirection.DirectionRatios
    
            axis_s = lp_space.RelativePlacement.Axis.DirectionRatios
    
            for lp_ref in lp_space.ReferencedByPlacements:
    
                location_ref = lp_ref.RelativePlacement.Location.Coordinates
    
                refDir_ref = lp_ref.RelativePlacement.RefDirection.DirectionRatios
    
                axis_ref = lp_ref.RelativePlacement.Axis.DirectionRatios
    
                ##
    
                z_s = np.array(axis_s) 
    
                x_s = np.array(refDir_ref) # _s for space
    
                x_s = x_s / norm(x_s)
    
                y_s = np.cross(z_norm, x_s)
    
                R_s = np.array([x_s, y_s, z_norm]).transpose()
    
                location_ref = wall_loc = np.array(location_ref)
    
                wall_glob = R_s.dot(wall_loc)
    
                refDir_space = x_b = np.array(refDir_s ) # _b for building storey
    
                z_b = np.array(axis_ref)
    
                x_b = x_b / norm(x_b)
    
                y_b = np.cross(z_norm, x_b)
    
                R_b = np.array([x_b, y_b, z_norm]).transpose()
    
                location_space = space_loc = np.array(location_s)
    
                space_glob = R_b.dot(space_loc)
    
                wall_tot_glob = space_glob + wall_glob
    
                wall_tot_loc = R_b.transpose().dot(wall_tot_glob)
    
                ##
    
                lp_ref.RelativePlacement.Location.Coordinates = (
    
                    float(wall_tot_glob[0]),
    
                    float(wall_tot_glob[1]),
    
                    float(wall_tot_glob[2])
    
                )
    
                lp_ref.PlacementRelTo = lp_space.PlacementRelTo
    
                lp_ref.RelativePlacement.RefDirection.DirectionRatios = (1.0, 0.0, 0.0)
    
  16. E

    @Jesusbill said:

    Mmm maybe I rushed a bit, let me work a bit on this and I will get back to you. This part of transformations is very interesting for me as well.

    By wall point I referred I guess to the position of the wall's local placement. But what I did does not consider the change of orientation of the walls local placement when removing the space.

    In any case there are three local placements, right? From building storey, space and wall and you want to find an updated local placement of the wall when removing the local placement of the space from the equation. I will work on this

    I think you only need to consider the two local placements, se the figure I made earlier:

  17. C

    have you considered simply removing the link with the geometric representation of the IfcSpace?

  18. S

    Must add the ifcSpace transform to localplacement of object with respect to remaining parent.

  19. M

    @c4rlosdias technically that is all that is required, and the code was written here. There is a bug in Solibri, perhaps due to an implementer agreement.

    I think @Einar is right, you just need to add one placement 3D to another. All other placements can be ignored. I haven't had the time to write some proof of concept code, but maybe you guys can work it out from this snippet: https://github.com/IfcOpenShell/IfcOpenShell/blob/2c9d6a47f4d24a923069775206bf734feeb14830/src/ifcbimtester/features/steps/model_federation.py#L8-L30

    See how here we apply the parent transformation? https://github.com/IfcOpenShell/IfcOpenShell/blob/2c9d6a47f4d24a923069775206bf734feeb14830/src/ifcbimtester/features/steps/model_federation.py#L30 - this is a recursive function which goes all the way up the spatial tree. You just need to remove the recursion and run it once.

    Another thing I would recommend when writing the code is once you calculate the new placement, do not edit the old one. Instead, create a whole new placement. This is because the old one could be used by multiple objects in theory, and may lead to scrambled geometry like in your screenshot, even if your new placement is calculated correctly.

    Hope it helps.

  20. E

    @Moult said:

    Another thing I would recommend when writing the code is once you calculate the new placement, do not edit the old one. Instead, create a whole new placement. This is because the old one could be used by multiple objects in theory, and may lead to scrambled geometry like in your screenshot, even if your new placement is calculated correctly.

    I tried to create new placements instead of editing the old ones, still problems with rotations. I'm learning a lot though :D

    The complete script so far:

    
    ifc_path = r"NB3_ARK.ifc"
    
    import numpy as np
    
    norm = np.linalg.norm
    
    import ifcopenshell
    
    print("Opening file at {} .....".format(ifc_path))
    
    f = ifcopenshell.open(ifc_path)
    
    spaces = f.by_type('IfcSpace')
    
    print("Iterating over {} spaces and iterates through the elements contained by the space and associates them with the spaces container".format(len(spaces)))
    
    for space in spaces:
    
        container = space.Decomposes[0].RelatingObject
    
        if len(space.ContainsElements) > 0:
    
            for contained_element in space.ContainsElements[0].RelatedElements:
    
                contained_element.ContainedInStructure[0].RelatingStructure = container
    
    print("Iterating over each spaces ObjectPlacement each element which is placed relative to it location and changes it to the container of the space.".format(len(local_placements)))     
    
    for space in spaces:
    
        lp_space = space.ObjectPlacement
    
        location_s = lp_space.RelativePlacement.Location.Coordinates
    
        axis_s = lp_space.RelativePlacement.Axis.DirectionRatios
    
        refDir_s = lp_space.RelativePlacement.RefDirection.DirectionRatios
    
        z_s = np.array(axis_s)
    
        x_s = np.array(refDir_s ) # _s for space
    
        x_s = x_s / norm(x_s)
    
        y_s = np.cross(z_s, x_s)
    
        R_s = np.array([x_s, y_s, z_s]).transpose()
    
        space_loc = np.array(location_s)
    
        space_glob = R_s.dot(space_loc)
    
        for lp_ref in lp_space.ReferencedByPlacements:
    
            location_ref = lp_ref.RelativePlacement.Location.Coordinates
    
            axis_ref = lp_ref.RelativePlacement.Axis.DirectionRatios
    
            refDir_ref = lp_ref.RelativePlacement.RefDirection.DirectionRatios
    
            z_r = np.array(axis_ref) 
    
            x_r = np.array(refDir_ref) # _r for ref
    
            x_r = x_r / norm(x_r)
    
            y_r = np.cross(z_r, x_r)
    
            R_r = np.array([x_r, y_r, z_r]).transpose()
    
            location_ref = np.array(location_ref)
    
            ref_glob = R_r.dot(location_ref)
    
            ref_tot_glob = space_glob + ref_glob
    
            ref_tot_loc = R_r.transpose().dot(ref_tot_glob)
    
            ##
    
            location = (
    
                float(ref_tot_loc[0]),
    
                float(ref_tot_loc[1]),
    
                float(ref_tot_loc[2])
    
            )
    
            axis  = (
    
                float(z_r[0]),
    
                float(z_r[1]),
    
                float(z_r[2])
    
            )
    
            refDir = (
    
                float(x_r[0]),
    
                float(x_r[1]),
    
                float(x_r[2])
    
            )
    
            new_lp = f.create_entity('IfcLocalPlacement')
    
            new_lp.RelativePlacement = f.createIfcAxis2Placement3D(
    
                f.createIfcCartesianPoint(location),
    
                f.createIfcDirection(axis),
    
                f.createIfcDirection(refDir)            
    
            )
    
            new_lp.PlacementRelTo = lp_space.PlacementRelTo
    
            lp_ref.PlacesObject[0].ObjectPlacement = new_lp
    
        #f.remove(lp_space)
    
    print("Deleting {} spaces".format(len(spaces)))
    
    for space in f.by_type("ifcspace"):
    
        f.remove(space)
    
    save_path = ifc_path[:-4]+"-space-rem.ifc"
    
    print("Saving file to {} ...".format(save_path))
    
    f.write(save_path)
    
    print("Done!")
    
  21. J

    @Einar with the material indicated by @Moult I think I have worked it out on the setup on your previous post, see attached file.

    My first approach is wrong conceptually for the location and does not handle the rotation of the local axis of the reference object.

    @Moult Any chance that the arguments in this line should be inverted? That is what I get from my testing.

  22. E

    Thank you @Jesusbill ! I must try and understand the code later, but it seems to work! Solibri is still struggeling with something though. Here is a side by side comparison between Solibri and XBim:

    the code:

    
    ifc_path = r"NB3_ARK.ifc"
    
    import numpy as np
    
    import ifcopenshell
    
    # Edit placements and delete the ones that places spaces
    
    def a2p(o, z, x):
    
        y = np.cross(z, x)
    
        r = np.eye(4)
    
        r[:-1,:-1] = x,y,z
    
        r[-1,:-1] = o
    
        return r.T
    
    def get_axis2placement(plc):
    
        z = np.array(plc.Axis.DirectionRatios if plc.Axis else (0,0,1))
    
        x = np.array(plc.RefDirection.DirectionRatios if plc.RefDirection else (1,0,0))
    
        o = plc.Location.Coordinates
    
        return a2p(o,z,x)
    
    def get_local_placement(plc):
    
        if plc is None:
    
            return np.eye(4)
    
        if plc.PlacementRelTo is None:
    
            parent = np.eye(4)
    
        else:
    
            parent = get_local_placement(plc.PlacementRelTo)
    
        # OR
    
        # return np.dot(get_axis2placement(plc.RelativePlacement), parent)
    
        # OR
    
        return np.dot(parent, get_axis2placement(plc.RelativePlacement))
    
    def create_axis2placement(f, x, z, o):
    
        x = f.createIfcDirection(x)
    
        z = f.createIfcDirection(z)
    
        o = f.createIfcCartesianPoint(o)
    
        axes = f.createIfcAxis2Placement3D(o, z, x)
    
        return axes
    
    import ifcopenshell
    
    print("Opening file at {} .....".format(ifc_path))
    
    f = ifcopenshell.open(ifc_path)
    
    spaces = f.by_type('IfcSpace')
    
    print("Iterating over {} spaces and iterates through the elements contained by the space and associates them with the spaces container".format(len(spaces)))
    
    for space in spaces:
    
        container = space.Decomposes[0].RelatingObject
    
        if len(space.ContainsElements) > 0:
    
            for contained_element in space.ContainsElements[0].RelatedElements:
    
                contained_element.ContainedInStructure[0].RelatingStructure = container
    
    print("Iterating over each spaces ObjectPlacement each element which is placed relative to it location and changes it to the container of the space.".format(len(local_placements)))     
    
    for space in spaces:
    
        lp_space = space.ObjectPlacement
    
        R_space = get_axis2placement(lp_space.RelativePlacement)
    
        plcRelTo_space = lp_space.PlacementRelTo
    
        for lp_ref in lp_space.ReferencedByPlacements:
    
            R_ref = get_axis2placement(lp_ref.RelativePlacement)
    
            # OR
    
            # R_new = R_ref.dot(R_space)
    
            # OR
    
            R_new = R_space.dot(R_ref)
    
            relPlc_new = create_axis2placement(
    
                f,
    
                x=R_new[:-1, 0].tolist(),
    
                z=R_new[:-1, 2].tolist(),
    
                o=R_new[:-1, -1].tolist()
    
            )
    
            plc_new = f.createIfcLocalPlacement(plcRelTo_space, relPlc_new)
    
            lp_ref.PlacesObject[0].ObjectPlacement = plc_new
    
        #f.remove(lp_space)
    
    print("Deleting {} spaces".format(len(spaces)))
    
    for space in f.by_type("ifcspace"):
    
        f.remove(space)
    
    save_path = ifc_path[:-4]+"-space-rem.ifc"
    
    print("Saving file to {} ...".format(save_path))
    
    f.write(save_path)
    
    import os
    
    os.startfile(save_path)
    
    print("Done!")
    
  23. E

    It seems that we can just set the import class mapping for IfcSpaces to DontImport and the spaces are gone :) (file > open > IFC Options)

  1. Page 1
  2. 2

Login or Register to reply.