Remove IfcSpaces with IfcOpenShell

2»

Comments

  • No it is not the most logical,
    rotate about absolute (nested relative result) location of current node should be.

  • 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

  • edited November 2020

    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

  • 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

  • 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.        ]
    
  • 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.

  • 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

  • edited November 2020

    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)
    
  • @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:

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

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

  • edited November 2020

    @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.

    Jesusbill
  • edited November 2020

    @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!")
    
  • @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.

  • edited November 2020

    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!")
    
    Jesusbill
  • It seems that we can just set the import class mapping for IfcSpaces to DontImport and the spaces are gone :) (file > open > IFC Options)

Sign In or Register to comment.