From 1d74306bc2af89a902d8a0f8d9d68465680d1b4c Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 13 Oct 2021 16:39:16 +1100 Subject: [PATCH] New recipe (and essay) to fix ArchiCAD IFC4 door swings showing up in Revit --- .../recipes/FixArchiCADToRevitDoorSwings.py | 177 ++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 src/ifcpatch/ifcpatch/recipes/FixArchiCADToRevitDoorSwings.py diff --git a/src/ifcpatch/ifcpatch/recipes/FixArchiCADToRevitDoorSwings.py b/src/ifcpatch/ifcpatch/recipes/FixArchiCADToRevitDoorSwings.py new file mode 100644 index 0000000000..50889f98f1 --- /dev/null +++ b/src/ifcpatch/ifcpatch/recipes/FixArchiCADToRevitDoorSwings.py @@ -0,0 +1,177 @@ +import os +import math +import ifcopenshell +import ifcopenshell.util.schema +import ifcopenshell.util.element + + +class Patcher: + def __init__(self, src, file, logger, args=None): + self.src = src + self.file = file + self.logger = logger + self.args = args + + def patch(self): + # ArchiCAD has the ability to store 2D data with objects like doors for + # door swings. ArchiCAD's implementation is not 100% correct (using + # footprint instead of annotation contexts), but otherwise not too + # shabby. + # + # Revit, however, is incapable of understanding this 2D representation. + # Revit users linking in IFCs produced by ArchiCAD may experience the + # following symptoms: + # + # A. Invisible doors, and difficulty selecting doors + # B. Invisible door swings, or only visible at particular view ranges + # C. Weird arc shapes around doors + # D. Extra lines around doors and walls + # E. Cannot easily change visibility graphics of 2D vs 3D elements + # F. Cannot view 2D data in a 3D view + # + # This is caused by the perfect storm of Revit IFC bugs, which we will + # work through methodically. + + # Revit has the ability to switch between 3D representations and 2D + # representations (e.g. in plan view). It does this by detecting IFC + # representations that belong to either the Model Body representation + # for 3D and the "Annotation" representation for 2D. Note: this is not a real + # "Annotation" representation. It is an invalid representation that is + # hardcoded in Revit. Nevertheless, if you follow their incorrect + # context naming convention by renaming ArchiCAD's also incorrect + # FootPrint context (but less incorrect) to Annotation, Revit will show + # 2D door representations as desired and switch between 2D and 3D + # intelligently. + # + # See bug https://github.com/Autodesk/revit-ifc/issues/187 + + #for context in self.file.by_type("IfcGeometricRepresentationSubContext"): + # if context.ContextIdentifier == "FootPrint": + # context.ContextIdentifier = None + # context.ContextType = "Annotation" + # context.ParentContext = [ + # c + # for c in self.file.by_type("IfcGeometricRepresentationContext", include_subtypes=False) + # if c.ContextType == "Plan" + # ][0] + + # However, we will not use this approach (hence it is commented out). + # This is because Revit will generate a single object with both a + # magically switching 2D and 3D representation - you cannot control both + # representations independently. Furthermore, the 2D representation is + # always generated at a Z value of 0. This means that the door swings + # are always invisible on any view range apart from those typically near + # an elevation of 0 which won't work for most projects. Because they are + # merged into a single object, you cannot fix the 2D elevation without + # breaking your 3D elevation. Even if we split it into 2D and 3D + # objects, the 2D object can only be moved up in 3D or section, but + # Revit makes the object invisible in 3D or section, so go fish. + # + # See symptom B. + + # Purge axis representations from walls. Revit will draw the axis (a + # line through the wall) but doesn't give you control over its + # visibilty, resulting in very ugly walls and lines typically cutting + # across door openings. + # + # See bug https://github.com/Autodesk/revit-ifc/issues/360 + # + # This fixes symptom D. + + for wall in self.file.by_type("IfcWall"): + reps = list(wall.Representation.Representations) + reps_excluding_axis = [r for r in reps if r.RepresentationIdentifier != "Axis"] + wall.Representation.Representations = reps_excluding_axis + + # We're going to split the 3D door body and the 2D door swing into two + # objects. Before we do that, we're going to cull the 2D representation + # from door types. If we didn't, this scenario may occur: + # + # 1. 3D IfcDoors with IfcDoorType import as Revit doors + # 2. 2D IfcBuildingElementProxy with no type import as Revit generic model + # 3. The Revit user turns off the visibilty of the door category. + # 4. The user is shocked to see that generic models are now invisible, + # even though it is clearly a separate category to doors. + # + # This should be a warning to all Revit users that unintuitively + # visibility graphics actually do _not_ affect the categories (e.g. + # Door, Window) for instances. Instead, they target categories for + # representation maps types, even if an instance is untyped. Yikes! + # + # See bug: https://github.com/Autodesk/revit-ifc/issues/362 + # + # This fixes symptom E. + + for door in self.file.by_type("IfcDoorType"): + rep_maps = list(door.RepresentationMaps or []) + door.RepresentationMaps = [ + r + for r in rep_maps + if r.MappedRepresentation + if r.MappedRepresentation.RepresentationIdentifier != "FootPrint" + ] + + # Let's now duplicate each door. The original door shall only retain its + # non 2D representations. The copied door will be changed into a + # "IfcDiscreteAccessory" so it can be filtered separately in Revit. + # Then, the copied door will only retain 2D representations. However, + # we're going to change the 2D representation context into a Model Body + # 3D context. This tricks Revit into converting the 2D data into 3D + # model line geometry (or something similar to that). + # + # See bug https://github.com/Autodesk/revit-ifc/issues/358 + # + # This fixes symptom A, B, and F. + + for door in self.file.by_type("IfcDoor"): + reps = list(door.Representation.Representations) + reps_excluding_footprint = [r for r in reps if r.RepresentationIdentifier != "FootPrint"] + footprint_reps = [r for r in reps if r.RepresentationIdentifier == "FootPrint"] + if not footprint_reps: + continue + door_copy = ifcopenshell.util.element.copy(self.file, door) + door_copy = ifcopenshell.util.schema.reassign_class(self.file, door_copy, "IfcDiscreteAccessory") + door_copy.Representation = ifcopenshell.util.element.copy(self.file, door.Representation) + door_copy.Representation.Representations = footprint_reps + door.Representation.Representations = reps_excluding_footprint + related_elements = list(door.ContainedInStructure[0].RelatedElements) + related_elements.append(door_copy) + door.ContainedInStructure[0].RelatedElements = related_elements + + body_context = [ + c for c in self.file.by_type("IfcGeometricRepresentationSubContext") if c.ContextIdentifier == "Body" + ][0] + for subelement in self.file.traverse(footprint_reps[0]): + if not subelement.is_a("IfcShapeRepresentation"): + continue + subelement.ContextOfItems = body_context + subelement.RepresentationIdentifier = "Body" + subelement.RepresentationType = "Curve3D" + + # Revit has bugs with handling arc index, which is relevant in IFC4. Our + # solution is to facet the arcs and swap out the polycurves with new + # faceted polycurves. + # + # See bug https://github.com/Autodesk/revit-ifc/issues/359 + # + # This fixes symptom C. + + settings = ifcopenshell.geom.settings() + settings.set(settings.INCLUDE_CURVES, True) + unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file) + for curve in self.file.by_type("IfcIndexedPolyCurve"): + if True in [s.is_a("IfcArcIndex") for s in curve.Segments]: + shape = ifcopenshell.geom.create_shape(settings, curve) + e = shape.edges + v = shape.verts + is_2d = curve.Points.is_a("IfcCartesianPointList2D") + if is_2d: + vertices = [[v[i] / unit_scale, v[i + 1] / unit_scale] for i in range(0, len(v), 3)] + else: + vertices = [ + [v[i] / unit_scale, v[i + 1] / unit_scale, v[i + 2] / unit_scale] for i in range(0, len(v), 3) + ] + edges = [[e[i] + 1, e[i + 1] + 1] for i in range(0, len(e), 2)] + points = [] + curve.Points = f.create_entity(curve.Points.is_a(), vertices) + curve.Segments = [f.createIfcLineIndex(e) for e in edges]