Bonsai | Copy IfcAnnotation leader from one IFC file to another with the product assignment data etc

edited August 2025 in General

Hi,
Let me explain my workflow, I had an IFC project , my teammate want to work on labelling the annotations in some elevations within the same project, we wanted to work , simultaneously, so he did the annotations.
The dimensions were easily appended using the project library and then assigned group.

but for the leaders wit product assignments, it was not easy, as all the relations with the objects gone.

, > Is there any easy way or better workflow? , I tried a python script after several iterations and it worked somehow :0, but not sure whether the ifc file is damaged, no errors in opening :),
The script is pasted below:

import os
import ifcopenshell
import numpy as np
from ifcopenshell.util.placement import get_local_placement

# ===== Paths =====
source_path = r"file_from_leader to be copied.ifc"
target_path = r"file_to _the leader is pasted.ifc"
save_path = r"path_to save_the_new_File.ifc"

# ===== Load files =====
source_ifc = ifcopenshell.open(source_path)
target_ifc = ifcopenshell.open(target_path)

# ===== IDs to copy =====
target_ids = ["0ta3j1AHvE98CkvIVBF_aC", "3kus5IHHf0Q8LaBDJglxSu", "1ZMfUHnzXBqvlF7ej6787n"]

# ===== Create base file from target =====
new_ifc = ifcopenshell.file(schema=target_ifc.schema)
for entity in target_ifc:
    new_ifc.add(entity)

# ===== Placement creation =====
def create_placement(ifc_file, location=(0, 0, 0), z_axis=(0, 0, 1), x_axis=(1, 0, 0)):
    """Create a simple IfcLocalPlacement at origin."""
    point = ifc_file.createIfcCartesianPoint(location)
    z_axis_dir = ifc_file.createIfcDirection(z_axis)
    x_axis_dir = ifc_file.createIfcDirection(x_axis)
    axis_placement = ifc_file.createIfcAxis2Placement3D(point, z_axis_dir, x_axis_dir)
    return ifc_file.createIfcLocalPlacement(None, axis_placement)

# ===== Deep copy placement =====
def deep_copy_placement(source_placement, target_file, copied_entities=None):
    """Recursively copy IfcLocalPlacement and dependencies."""
    if source_placement is None:
        return None

    if copied_entities is None:
        copied_entities = {}

    if source_placement.id() in copied_entities:
        return copied_entities[source_placement.id()]

    # Copy PlacementRelTo recursively
    placement_relto = None
    if hasattr(source_placement, 'PlacementRelTo') and source_placement.PlacementRelTo:
        placement_relto = deep_copy_placement(source_placement.PlacementRelTo, target_file, copied_entities)

    # Copy RelativePlacement
    relative_placement = None
    if hasattr(source_placement, 'RelativePlacement') and source_placement.RelativePlacement:
        rp = source_placement.RelativePlacement
        point = target_file.createIfcCartesianPoint(tuple(rp.Location.Coordinates))
        z_dir = target_file.createIfcDirection(tuple(rp.Axis.DirectionRatios)) if rp.Axis else None
        x_dir = target_file.createIfcDirection(tuple(rp.RefDirection.DirectionRatios)) if rp.RefDirection else None
        relative_placement = target_file.createIfcAxis2Placement3D(point, z_dir, x_dir)

    # Create the new placement
    new_placement = target_file.createIfcLocalPlacement(placement_relto, relative_placement)
    copied_entities[source_placement.id()] = new_placement
    return new_placement

# ===== Deep copy any IFC entity (for geometry) =====
def deep_copy_entity(entity, target_file, copied_entities):
    """Generic recursive entity copier."""
    if entity is None:
        return None
    if entity.id() in copied_entities:
        return copied_entities[entity.id()]

    kwargs = {}
    for k, v in entity.get_info().items():
        if k in ['id', 'type'] or v is None:
            continue
        if hasattr(v, 'is_a'):  # nested IFC entity
            kwargs[k] = deep_copy_entity(v, target_file, copied_entities)
        elif isinstance(v, (list, tuple)):
            new_list = []
            for val in v:
                if hasattr(val, 'is_a'):
                    new_list.append(deep_copy_entity(val, target_file, copied_entities))
                else:
                    new_list.append(val)
            kwargs[k] = tuple(new_list)
        else:
            kwargs[k] = v

    new_ent = target_file.create_entity(entity.is_a(), **kwargs)
    copied_entities[entity.id()] = new_ent
    return new_ent

# ===== Deep copy representation =====
def deep_copy_representation(source_rep, target_file, copied_entities=None):
    if copied_entities is None:
        copied_entities = {}
    return deep_copy_entity(source_rep, target_file, copied_entities)

# ===== Main loop =====
for guid in target_ids:
    annotation = source_ifc.by_guid(guid)
    if not annotation:
        print(f"❌ Source annotation {guid} not found")
        continue

    print(f"\nProcessing {guid} ({annotation.is_a()})")

    # Copy placement deeply or create at origin
    if not hasattr(annotation, 'ObjectPlacement') or not annotation.ObjectPlacement:
        placement = create_placement(new_ifc)
        print("📌 Created new placement at origin")
    else:
        try:
            placement = deep_copy_placement(annotation.ObjectPlacement, new_ifc)
            print("📌 Copied existing placement (deep copy)")
        except:
            placement = create_placement(new_ifc)
            print("⚠️ Failed to copy placement, created new one at origin")

    # Copy annotation entity (without placement/representation)
    new_annotation = new_ifc.create_entity(
        annotation.is_a(),
        **{k: v for k, v in annotation.get_info().items()
           if k not in ['id', 'type', 'ObjectPlacement', 'Representation'] and v is not None}
    )
    new_annotation.ObjectPlacement = placement

    # Copy representation deeply (including IndexedPolyCurve + point list)
    if hasattr(annotation, 'Representation') and annotation.Representation:
        representation = deep_copy_representation(annotation.Representation, new_ifc)
        new_annotation.Representation = representation
        print("📐 Deep copied representation with geometry")

    # Copy product assignment
    for rel in getattr(annotation, "HasAssignments", []):
        if rel.is_a("IfcRelAssignsToProduct"):
            product_guid = rel.RelatingProduct.GlobalId
            target_product = new_ifc.by_guid(product_guid)
            if target_product:
                new_ifc.create_entity(
                    "IfcRelAssignsToProduct",
                    GlobalId=ifcopenshell.guid.new(),
                    OwnerHistory=target_product.OwnerHistory,
                    Name=rel.Name or "Annotation Link",
                    RelatedObjects=[new_annotation],
                    RelatingProduct=target_product
                )
                print(f"📎 Linked to product {product_guid}")

    print(f"✅ Successfully processed {guid}")

# ===== Save file =====
print("\nSaving file...")
new_ifc.write(save_path)
print(f"✅ File saved to: {save_path}")

# ===== Verification =====
print("\n=== FINAL VERIFICATION ===")
try:
    saved_ifc = ifcopenshell.open(save_path)
    for guid in target_ids:
        annotation = saved_ifc.by_guid(guid)
        if annotation:
            print(f"\n🔍 {guid} verification:")
            print(f"Type: {annotation.is_a()}")

            # Placement check
            if hasattr(annotation, 'ObjectPlacement') and annotation.ObjectPlacement:
                try:
                    matrix = get_local_placement(annotation.ObjectPlacement)
                    print("Placement matrix:")
                    print(np.array_str(matrix, precision=2))
                except Exception as e:
                    print(f"⚠️ Placement error: {str(e)}")
            else:
                print("❌ Missing ObjectPlacement")

            # Representation check
            if hasattr(annotation, 'Representation') and annotation.Representation:
                print(f"Representation type: {annotation.Representation.is_a()}")
                if annotation.Representation.is_a('IfcProductDefinitionShape'):
                    reps = getattr(annotation.Representation, 'Representations', [])
                    print(f"- {len(reps)} representations")
                    for rep in reps:
                        print(f"  - {rep.is_a()} with {len(getattr(rep, 'Items', []))} items")
            else:
                print("⚠️ No representation found")

            print("✅ Verification complete")
        else:
            print(f"❌ {guid} not found in saved file")
except Exception as e:
    print(f"⚠️ Verification error: {str(e)}")

print("\nProcess completed successfully")
Tagged:

Comments

  • edited August 2025

    Have you tried the git workflow that's native with Bonsai? Would allow multiple collaborators to work concurrently on the 'same model'. Just be aware not to modify anything that your collaborator will touch, which can be hard to accomplish sometimes. @brunopostle has recently improved the merging of small conflicts. I'm not sure these changes, however, made it into the latest release yet.

    BTW, i think once cracked, this Git workflow with IFC and Bonsai is one of the more ground breaking parts of using Bonsai. I don't think the typical user has really realized its potential yet.

    BedsonsteverugiNigelwalpa
  • @theoryshaw said:
    Have you tried the git workflow ... Just be aware not to modify anything that your collaborator will touch,

    Yeah , tried the git workflow , its wonderful.
    I deleted an from object from my branch which was present in my collaborators branch, which made the file merge conflict.
    Without modifying same elements, its good.

  • @arunarchitect said:

    @theoryshaw said:
    Have you tried the git workflow ... Just be aware not to modify anything that your collaborator will touch,

    Yeah , tried the git workflow , its wonderful.
    I deleted an from object from my branch which was present in my collaborators branch, which made the file merge conflict.

    The workaround for this is to delete the modified element (so it is deleted in both branches) and the merge will work. If you want to keep the modified element, duplicate it before deleting it.

    arunarchitectzoomer
Sign In or Register to comment.