1

I have a model with many elements that is classified as ifcbuildingelementproxy (or unclassified as that is the standard of the ifc exporting software aka ifcObject). I have a code that finds all the elements i want to change the classification of, but i cant seem to find a way to change it. What i want to do is to get my script to reclassify all elements whos name begins with "whatever" to IfcWindow instead of IfcBuildingElementProxy.

def re_classify():
    ifc_loc='thefile.ifc'
    ifcfile = ifcopenshell.open(ifc_loc)
    create_guid = lambda: ifcopenshell.guid.compress(uuid.uuid1().hex)
    owner_history = ifcfile.by_type("IfcOwnerHistory")[0]
    element = ifcfile.by_type("IfcElement")
    sets = ifcfile.by_type("IfcPropertySet")
    search_word_in_name='M_Muntin'
    for e in element:
        if e.is_a('IfcBuildingElementProxy'):
            if e.Name.startswith(search_word_in_name,0,len(search_word_in_name)):
                e.is_a()==e.is_a('IfcWindow')   #<--- THIS DOES NOTHING
                print (e)
                print(e.Name,' - ', e.is_a())

re_classify()

I expect that f.ex

# 13505=IfcBuildingElementProxy('3OAbz$kW1DyuZY2KLwUwkk',#41,'M_Muntin Pattern_2x2:M_Muntin Pattern_2x2:346152',$,'M_Muntin Pattern_2x2',#13504,#13499,'346152',$)

will show

# 13505=IfcWindow('3OAbz$kW1DyuZY2KLwUwkk',#41,'M_Muntin Pattern_2x2:M_Muntin Pattern_2x2:346152',$,'M_Muntin Pattern_2x2',#13504,#13499,'346152',$)
sanzoghenzo
  • 598
  • 5
  • 21
Thomas M
  • 56
  • 6
  • Your window instance as written would be invalid as it should have one (IFC2x3) or three (IFC4) more attributes. – hlg Nov 27 '19 at 20:29

3 Answers3

1

On a Unix/Linux shell, you can make the substitution using a one-liner (without Python or IfcOpenShell), as follows:

sed -i '/IFCBUILDINGELEMENTPROXY(.*,.*,.M_Muntin/{s/IFCBUILDINGELEMENTPROXY/IFCWINDOW/}' thefile.ifc

Note that it makes the modifications directly in the initial file.

(N.B.: Tested on Cygwin)

0

You can not simply change the type. It is not possible to assign a value to a function and is_a is a function and not an attribute (by design for the reason that the type is unmodifiable).

Further IfcBuildingElementProxy and IfcWindow share a subset of their attributes only. That is IfcBuildingElementProxy has one that IfcWindow does not have and vice versa. Fortunately, the additional attributes of IfcWindow are all optional. So you could create a new window entity, copy the common attributes from the proxy, leave the additional attributes unset and remove the proxy.

commonAttrs = list(e.get_Info().values())[2:-1]
window = ifcfile.createIfcWindow(*commonAttrs)
ifcfile.remove(e)

You still would have to look for other entities referencing the proxy and replace the references with references to the window to obtain a valid ifc file.

hlg
  • 1,321
  • 13
  • 29
0

Thanks to the other answers and some more research, I've put up the following code to accomplish this task.

If the number of attributes that cannot be set in the new type instance is more than 5, the update is not performed.

It seems to work well for now, not entirely sure if it's enough to check the RelatedElements and RelatedObjects or if there are more attributes that needs to be checked.

def change_element_type(
    element: entity_instance,
    new_type: str,
    model: ifcopenshell.file,
) -> None:
    """
    Change the element type and pass all the compatible properties.

    GlobalId is kept, Element ID is not.
    If the new type misses more than 5 of the old attributes,
    the change is avoided.

    Args:
        element: original ifc element
        new_type: type to change the element to
        model: IFC model containing the element
    """
    if new_type == element.is_a():
        return
    new_element = model.create_entity(new_type)
    old_attrs = element.get_info(include_identifier=False)
    del old_attrs["type"]
    new_attrs = new_element.get_info(include_identifier=False)
    missing_attrs = set(old_attrs) - set(new_attrs)
    if len(missing_attrs) > 5:
        warnings.warn(
            f"New type {new_type} for element {element.id} "
            f"misses too many attributes:\n {', '.join(missing_attrs)}.\n"
            "Type change is cancelled."
        )
        model.remove(new_element)
        return
    for name in new_attrs:
        if name in old_attrs:
            setattr(new_element, name, old_attrs[name])
    update_references(element, model, new_element)
    model.remove(element)


def update_references(element, model, new_element):
    for to_update in model.get_inverse(element):
        for attr in ("RelatedElements", "RelatedObjects"):
            try:
                rel_objs = list(getattr(to_update, attr))
            except AttributeError:
                continue
            try:
                rel_objs.remove(element)
            except ValueError:
                continue
            rel_objs.append(new_element)
            setattr(to_update, attr, rel_objs)
sanzoghenzo
  • 598
  • 5
  • 21