mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 01:11:40 +00:00
New recipe (and essay) to fix ArchiCAD IFC4 door swings showing up in Revit
This commit is contained in:
@@ -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]
|
||||
Reference in New Issue
Block a user