Point cloud link in IFC Project

edited August 2024 in General

Hello! What is your favourite way to load a point cloud within an IFC so you do not have to reload it each time?
I use Point Cloud Visualizer, and I find it perfect, except that I have to reload the point cloud each time I need to revert the IFC Project.

Tagged:

Comments

  • Hi! Can you please show an example how it currently works for you with Point Cloud Visualizer?

  • edited August 2024

    Here you can find the steps:
    1) Load an Ifc file, create an empty blender object, create a blender cube object and set its viewport visibility to "Wire"

    2) Select the empty object and load the ply file with PCV, rotate it and align it with the model;

    3) Enable clipping and set the cube as clipping object;

    4) Repeat this operation for the other point clouds I need (generally at least 2-3: one for the external building and several for the interior).

    Usually set just one clipping object for all the point clouds, so I can clip them together.

    theoryshawMassimoAceAndrej730tlang
  • If it helps, you can store point cloud data in IFC by converting it to mesh after step 2 - https://jakubuhlik.com/docs/pcv/convert.html#mesh and then assigning IFC class. Then when you reopen IFC, you can generate PCV data from point cloud object's mesh vertices - https://jakubuhlik.com/docs/pcv/generate.html#mesh-vertices

    But IFC doesn't have options to store any point cloud data besides the positions (such as colors, sizes, normals, ets) - https://github.com/IfcOpenShell/IfcOpenShell/issues/363. And it seems there is no native IFC way to link some .ply file to the element/representation.

  • edited August 2024

    Thanks @Andrej730 for the advice! The project i'm working on has more than 6 Gbyte of point cloud data, so I'd prefer to avoid to have it stored inside the IFC file.
    Just thinking out loud, but what I'd think that could be nice would be to have it linked as a document (ProjectDocumentInformation ?) like we do with drawings (as they also are in the end external resources).
    So in the "Drawings and Documents" tab we could host a dedicated menu for it, and in case PCV is installed, .... well, i don't know ... :)
    Or perhaps something in the References tab under "Drawings and Documents"?

    tlangJohnKoAra
  • I can share a script I've used in the past when I had the same problem. Nowadays I elect on using the same Blend file all along the life of my project (which has its own quirks when things get corrupted but this happens WAYYY less than a few versions ago) so I don't need to reload PCV settings.

    This creates a Pset on an IFC element that acts a proxy for the point cloud. It stores a number PCV settings and the clipping cube transform. There are two operators, one to store the settings when you quit Blender, and one to restore the settings when you imported the project in a blank blend file.

    This should work as is, if you don't see the operators with F3 you need to enable developer extras in the preferences. You can add properties to store / restore by modifying the paths and property name in pcv_mapping_dict

    import bpy
    from mathutils import Matrix
    import json
    
    try:
        from ifcopenshell.api import run
        from blenderbim.bim.ifc import IfcStore
        import blenderbim.tool as tool
    except:
        pass
    finally:
        pcv_mapping_dict = {
            "File Path": ("point_cloud_visualizer.data", "filepath"),
            "Shader": ("point_cloud_visualizer.shader", "type"),
            "Percentage": ("point_cloud_visualizer.display", "percentage"),
            "Point Size": ("point_cloud_visualizer.display", "point_size"),
            "Alpha": ("point_cloud_visualizer.display", "global_alpha"),
            "Clip": ("point_cloud_visualizer.shader", "clip_enabled"),
            "Clip Planes Live Update": ("point_cloud_visualizer.shader", "clip_planes_from_bbox_object_live"),
            "Use Scalars": ("point_cloud_visualizer.display", "use_scalar"),
            "Scalar Use Scheme": ("point_cloud_visualizer.display", "use_scheme"),
            "Scalar Scheme": ("point_cloud_visualizer.display", "scheme"),
            "Scalar Opacity": ("point_cloud_visualizer.display", "bleeding"),
            "Scalar Mode": ("point_cloud_visualizer.display", "blending_mode"),
        }
    
        class GU_OT_IFC_PCV_update_settings(bpy.types.Operator):
            bl_idname = "ifc.pcv_update_settings"
            bl_label = "Update PCV Settings"
            bl_options = {"REGISTER", "UNDO"}
    
            @classmethod
            def poll(cls, context):
                obj = context.active_object
                if obj is None:
                    return
                element = tool.Ifc.get_entity(obj)
                if not element:
                    return
                return bool(tool.Pset.get_element_pset(element, "PointCloudVisualizerProps"))
    
            def execute(self, context):
                obj = context.active_object
                element = tool.Ifc.get_entity(obj)
                pset = tool.Pset.get_element_pset(element, "PointCloudVisualizerProps")
                for prop in pset.HasProperties:
                    value = prop.NominalValue.wrappedValue
                    if prop.Name == "Clip Object Name":
                        if not value:
                            continue
                        obj_clip = bpy.data.objects.get(value)
                        if not obj_clip:
                            bpy.ops.mesh.primitive_cube_add(
                                size=2, enter_editmode=False, align="WORLD", location=(0, 0, 0), scale=(1, 1, 1)
                            )
                            obj_clip = context.view_layer.objects.active
                            obj_clip.name = value
                            obj_clip.display_type = "BOUNDS"
                            obj_clip.select_set(False)
                            context.view_layer.objects.active = obj
                            obj.select_set(True)
                        obj.point_cloud_visualizer.shader.clip_planes_from_bbox_object = obj_clip
                        clip_obj_matrix_prop = next(
                            (p for p in pset.HasProperties if p.Name == "Clip Object Matrix"), None
                        )
                        if clip_obj_matrix_prop:
                            matrix_1d = json.loads(clip_obj_matrix_prop.NominalValue.wrappedValue)
                            matrix = [
                                (
                                    matrix_1d[4 * i],
                                    matrix_1d[4 * i + 1],
                                    matrix_1d[4 * i + 2],
                                    matrix_1d[4 * i + 3],
                                )
                                for i in range(4)
                            ]
                            obj_clip.matrix_world = Matrix(matrix)
    
                    elif prop.Name == "Scalar Range Min":
                        obj.point_cloud_visualizer.display.range[0] = value
                    elif prop.Name == "Scalar Range Max":
                        obj.point_cloud_visualizer.display.range[1] = value
                    elif prop.Name in pcv_mapping_dict:
                        prop_path, attr = pcv_mapping_dict[prop.Name]
                        setattr(obj.path_resolve(prop_path), attr, value)
    
                return {"FINISHED"}
    
        class GU_OT_IFC_PCV_store_settings(bpy.types.Operator):
            bl_idname = "ifc.pcv_store_settings"
            bl_label = "Store PCV Settings"
            bl_options = {"REGISTER", "UNDO"}
    
            def execute(self, context):
                obj = context.active_object
                ifc = tool.Ifc.get()
                element = tool.Ifc.get_entity(obj)
    
                pset = run("pset.add_pset", ifc, product=element, name="PointCloudVisualizerProps")
    
                properties = {k: getattr(obj.path_resolve(v[0]), v[1]) for k, v in pcv_mapping_dict.items()}
                clip_object = obj.point_cloud_visualizer.shader.clip_planes_from_bbox_object
                properties["Clip Object Name"] = clip_object.name if clip_object else None
                if clip_object:
                    rows = clip_object.matrix_world.row
                    matrix_1d = []
                    for row in rows:
                        for elt in row:
                            matrix_1d.append(elt)
                    properties["Clip Object Matrix"] = json.dumps(matrix_1d)
                properties["Scalar Range Min"] = obj.point_cloud_visualizer.display.range[0]
                properties["Scalar Range Max"] = obj.point_cloud_visualizer.display.range[1]
    
                run("pset.edit_pset", ifc, pset=pset, properties=properties)
                ifc.write(IfcStore.path)
    
                context.view_layer.objects.active = context.active_object
    
                return {"FINISHED"}
    
        bpy.utils.register_class(GU_OT_IFC_PCV_update_settings)
        bpy.utils.register_class(GU_OT_IFC_PCV_store_settings)
    
  • I can share a script I've used in the past when I faced the same problem. I don't use it anymore since I have since elected to using the same Blend file all throughout the project, which has its own drawbacks but far less than a few versions ago.

    This creates a Pset on the selected IFC object with relevant PCV data. It also stores the clipping box transform.
    You should be able to run this in the script editor and F3 > Store PCV Settings or F3 > Update PCV Settings. If you don't see these prompts enable Developer Extras in the preferences. You can setup other properties in the pcv_mapping_dict dictionary.

    import bpy
    from mathutils import Matrix
    import json
    
    try:
        from ifcopenshell.api import run
        from blenderbim.bim.ifc import IfcStore
        import blenderbim.tool as tool
    except:
        pass
    finally:
        pcv_mapping_dict = {
            "File Path": ("point_cloud_visualizer.data", "filepath"),
            "Shader": ("point_cloud_visualizer.shader", "type"),
            "Percentage": ("point_cloud_visualizer.display", "percentage"),
            "Point Size": ("point_cloud_visualizer.display", "point_size"),
            "Alpha": ("point_cloud_visualizer.display", "global_alpha"),
            "Clip": ("point_cloud_visualizer.shader", "clip_enabled"),
            "Clip Planes Live Update": ("point_cloud_visualizer.shader", "clip_planes_from_bbox_object_live"),
            "Use Scalars": ("point_cloud_visualizer.display", "use_scalar"),
            "Scalar Use Scheme": ("point_cloud_visualizer.display", "use_scheme"),
            "Scalar Scheme": ("point_cloud_visualizer.display", "scheme"),
            "Scalar Opacity": ("point_cloud_visualizer.display", "bleeding"),
            "Scalar Mode": ("point_cloud_visualizer.display", "blending_mode"),
        }
    
        class GU_OT_IFC_PCV_update_settings(bpy.types.Operator):
            bl_idname = "ifc.pcv_update_settings"
            bl_label = "Update PCV Settings"
            bl_options = {"REGISTER", "UNDO"}
    
            @classmethod
            def poll(cls, context):
                obj = context.active_object
                if obj is None:
                    return
                element = tool.Ifc.get_entity(obj)
                if not element:
                    return
                return bool(tool.Pset.get_element_pset(element, "PointCloudVisualizerProps"))
    
            def execute(self, context):
                obj = context.active_object
                element = tool.Ifc.get_entity(obj)
                pset = tool.Pset.get_element_pset(element, "PointCloudVisualizerProps")
                for prop in pset.HasProperties:
                    value = prop.NominalValue.wrappedValue
                    if prop.Name == "Clip Object Name":
                        if not value:
                            continue
                        obj_clip = bpy.data.objects.get(value)
                        if not obj_clip:
                            bpy.ops.mesh.primitive_cube_add(
                                size=2, enter_editmode=False, align="WORLD", location=(0, 0, 0), scale=(1, 1, 1)
                            )
                            obj_clip = context.view_layer.objects.active
                            obj_clip.name = value
                            obj_clip.display_type = "BOUNDS"
                            obj_clip.select_set(False)
                            context.view_layer.objects.active = obj
                            obj.select_set(True)
                        obj.point_cloud_visualizer.shader.clip_planes_from_bbox_object = obj_clip
                        clip_obj_matrix_prop = next(
                            (p for p in pset.HasProperties if p.Name == "Clip Object Matrix"), None
                        )
                        if clip_obj_matrix_prop:
                            matrix_1d = json.loads(clip_obj_matrix_prop.NominalValue.wrappedValue)
                            matrix = [
                                (
                                    matrix_1d[4 * i],
                                    matrix_1d[4 * i + 1],
                                    matrix_1d[4 * i + 2],
                                    matrix_1d[4 * i + 3],
                                )
                                for i in range(4)
                            ]
                            obj_clip.matrix_world = Matrix(matrix)
    
                    elif prop.Name == "Scalar Range Min":
                        obj.point_cloud_visualizer.display.range[0] = value
                    elif prop.Name == "Scalar Range Max":
                        obj.point_cloud_visualizer.display.range[1] = value
                    elif prop.Name in pcv_mapping_dict:
                        prop_path, attr = pcv_mapping_dict[prop.Name]
                        setattr(obj.path_resolve(prop_path), attr, value)
    
                return {"FINISHED"}
    
        class GU_OT_IFC_PCV_store_settings(bpy.types.Operator):
            bl_idname = "ifc.pcv_store_settings"
            bl_label = "Store PCV Settings"
            bl_options = {"REGISTER", "UNDO"}
    
            def execute(self, context):
                obj = context.active_object
                ifc = tool.Ifc.get()
                element = tool.Ifc.get_entity(obj)
    
                pset = run("pset.add_pset", ifc, product=element, name="PointCloudVisualizerProps")
    
                properties = {k: getattr(obj.path_resolve(v[0]), v[1]) for k, v in pcv_mapping_dict.items()}
                clip_object = obj.point_cloud_visualizer.shader.clip_planes_from_bbox_object
                properties["Clip Object Name"] = clip_object.name if clip_object else None
                if clip_object:
                    rows = clip_object.matrix_world.row
                    matrix_1d = []
                    for row in rows:
                        for elt in row:
                            matrix_1d.append(elt)
                    properties["Clip Object Matrix"] = json.dumps(matrix_1d)
                properties["Scalar Range Min"] = obj.point_cloud_visualizer.display.range[0]
                properties["Scalar Range Max"] = obj.point_cloud_visualizer.display.range[1]
    
                run("pset.edit_pset", ifc, pset=pset, properties=properties)
                ifc.write(IfcStore.path)
    
                context.view_layer.objects.active = context.active_object
    
                return {"FINISHED"}
    
        bpy.utils.register_class(GU_OT_IFC_PCV_update_settings)
        bpy.utils.register_class(GU_OT_IFC_PCV_store_settings)
    
    carlopavJohn
Sign In or Register to comment.