Page MenuHomec4science

read_boundaries.py
No OneTemporary

File Metadata

Created
Wed, Jul 31, 06:32

read_boundaries.py

# coding: utf8
"""This module reads IfcRelSpaceBoundary from an IFC file and display them in FreeCAD"""
import os
import ifcopenshell
import ifcopenshell.geom
import FreeCAD
import FreeCADGui
import Part
import uuid
def ios_settings(brep):
"""Create ifcopenshell.geom.settings for various cases"""
settings = ifcopenshell.geom.settings()
settings.set(settings.EXCLUDE_SOLIDS_AND_SURFACES, False)
settings.set(settings.INCLUDE_CURVES, True)
if brep:
settings.set(settings.USE_BREP_DATA, True)
return settings
BREP_SETTINGS = ios_settings(brep=True)
MESH_SETTINGS = ios_settings(brep=False)
"""With IfcOpenShell 0.6.0a1 recreating face from wires seems to give more consistant results.
Especially when inner boundaries touch outer boundary"""
BREP = False
# IfcOpenShell/IFC default unit is m, FreeCAD internal unit is mm
SCALE = 1000
def display_boundaries(ifc_path, doc=FreeCAD.ActiveDocument):
"""Display IfcRelSpaceBoundaries from selected IFC file into FreeCAD documennt"""
# Create default groups
group = get_or_create_group(doc, "RelSpaceBoundary")
group.addProperty("App::PropertyString", "ApplicationIdentifier")
group.addProperty("App::PropertyString", "ApplicationVersion")
group_2nd = get_or_create_group(doc, "SecondLevel")
group.addObject(group_2nd)
ifc_file = ifcopenshell.open(ifc_path)
owning_application = ifc_file.by_type("IfcApplication")[0]
group.ApplicationIdentifier = owning_application.ApplicationIdentifier
group.ApplicationVersion = owning_application.Version
spaces = ifc_file.by_type("IfcSpace")
for space in spaces:
space_full_name = f"{space.Name} {space.LongName}"
space_group = group_2nd.newObject("App::DocumentObjectGroup", f"Space_{space.Name}")
space_group.Label = space_full_name
# All boundaries have their placement relative to space placement
space_placement = get_placement(space)
for ifc_boundary in (b for b in space.BoundedBy if b.Name == "2ndLevel"):
face = make_relspaceboundary("Face")
space_group.addObject(face)
face.Shape = create_fc_shape(ifc_boundary)
face.Placement = space_placement
face.Label = "{} {}".format(
ifc_boundary.RelatedBuildingElement.id(),
ifc_boundary.RelatedBuildingElement.Name,
)
face.ViewObject.ShapeColor = get_color(ifc_boundary.RelatedBuildingElement)
face.GlobalId = ifc_boundary.GlobalId
face.InternalOrExternalBoundary = ifc_boundary.InternalOrExternalBoundary
face.PhysicalOrVirtualBoundary = ifc_boundary.PhysicalOrVirtualBoundary
# face.RelatedBuildingElement = get_or_create_wall(ifc_boundary.RelatedBuildingElement)
# face.OriginalBoundary = face
for space in spaces:
# Find inner boundaries
for ifc_boundary in (b for b in space.BoundedBy if b.Name == "2ndLevel"):
try:
if ifc_boundary.ParentBoundary:
pass
except AttributeError:
pass
create_geo_ext_boundaries(doc, group_2nd)
create_geo_int_boundaries(doc, group_2nd)
doc.recompute()
def get_or_create_wall(ifc_wall):
return
def get_wall_thickness(ifc_wall):
wall_thickness = 0
for association in ifc_wall.HasAssociations:
if not association.is_a("IfcRelAssociatesMaterial"):
continue
for material_layer in association.RelatingMaterial.ForLayerSet.MaterialLayers:
wall_thickness += material_layer.LayerThickness
return wall_thickness
def create_geo_ext_boundaries(doc, group_2nd):
group_geo_ext = doc.copyObject(group_2nd, True) # True = whith_dependencies
group_geo_ext.Label = "geoExt"
is_from_revit = group_2nd.getParentGroup().ApplicationIdentifier == "Revit"
for fc_space in group_geo_ext.Group:
for fc_boundary in fc_space.Group:
wall_thickness = 200
if fc_boundary.InternalOrExternalBoundary != "INTERNAL":
lenght = wall_thickness
if is_from_revit:
lenght /= 2
fc_boundary.Placement.move(fc_boundary.Shape.normalAt(0, 0) * lenght)
else:
lenght = wall_thickness / 2
if is_from_revit:
continue
fc_boundary.Placement.move(fc_boundary.Shape.normalAt(0, 0) * lenght)
def create_geo_int_boundaries(doc, group_2nd):
group_geo_int = doc.copyObject(group_2nd, True) # True = whith_dependencies
group_geo_int.Label = "geoInt"
is_from_revit = group_2nd.getParentGroup().ApplicationIdentifier == "Revit"
for fc_space in group_geo_int.Group:
for fc_boundary in fc_space.Group:
if fc_boundary.InternalOrExternalBoundary != "INTERNAL":
if is_from_revit:
wall_thickness = 200
lenght = -wall_thickness / 2
fc_boundary.Placement.move(
fc_boundary.Shape.normalAt(0, 0) * lenght
)
def create_fc_shape(space_boundary):
""" Create Part shape from ifc geometry"""
if BREP:
try:
return _part_by_brep(
space_boundary.ConnectionGeometry.SurfaceOnRelatingElement
)
except RuntimeError:
print(f"Failed to generate brep from {space_boundary}")
fallback = True
if not BREP or fallback:
try:
return part_by_wires(
space_boundary.ConnectionGeometry.SurfaceOnRelatingElement
)
except RuntimeError:
print(f"Failed to generate mesh from {space_boundary}")
return _part_by_mesh(
space_boundary.ConnectionGeometry.SurfaceOnRelatingElement
)
def _part_by_brep(ifc_entity):
""" Create a Part Shape from brep generated by ifcopenshell from ifc geometry"""
ifc_shape = ifcopenshell.geom.create_shape(BREP_SETTINGS, ifc_entity)
fc_shape = Part.Shape()
fc_shape.importBrepFromString(ifc_shape.brep_data)
fc_shape.scale(SCALE)
return fc_shape
def _part_by_mesh(ifc_entity):
""" Create a Part Shape from mesh generated by ifcopenshell from ifc geometry"""
return Part.Face(_polygon_by_mesh(ifc_entity))
def _polygon_by_mesh(ifc_entity):
"""Create a Polygon from a compatible ifc entity"""
ifc_shape = ifcopenshell.geom.create_shape(MESH_SETTINGS, ifc_entity)
ifc_verts = ifc_shape.verts
fc_verts = [
FreeCAD.Vector(ifc_verts[i : i + 3]).scale(SCALE, SCALE, SCALE)
for i in range(0, len(ifc_verts), 3)
]
fc_verts = verts_clean(fc_verts)
return Part.makePolygon(fc_verts)
def verts_clean(vertices):
"""For some reason, vertices are not always clean and sometime a same vertex is repeated"""
new_verts = list()
for i in range(len(vertices) - 1):
if vertices[i] != vertices[i + 1]:
new_verts.append(vertices[i])
new_verts.append(vertices[-1])
return new_verts
def part_by_wires(ifc_entity):
""" Create a Part Shape from ifc geometry"""
boundaries = list()
boundaries.append(_polygon_by_mesh(ifc_entity.OuterBoundary))
try:
inner_boundaries = ifc_entity.InnerBoundaries
for inner_boundary in tuple(inner_boundaries) if inner_boundaries else tuple():
boundaries.append(_polygon_by_mesh(inner_boundary))
except RuntimeError:
pass
fc_shape = Part.makeFace(boundaries, "Part::FaceMakerBullseye")
matrix = get_matrix(ifc_entity.BasisSurface.Position)
fc_shape = fc_shape.transformGeometry(matrix)
return fc_shape
def get_matrix(position):
"""Transform position to FreeCAD.Matrix"""
location = FreeCAD.Vector(position.Location.Coordinates).scale(SCALE, SCALE, SCALE)
v_1 = FreeCAD.Vector(position.RefDirection.DirectionRatios)
v_3 = FreeCAD.Vector(position.Axis.DirectionRatios)
v_2 = v_3.cross(v_1)
# fmt: off
matrix = FreeCAD.Matrix(
v_1.x, v_2.x, v_3.x, location.x,
v_1.y, v_2.y, v_3.y, location.y,
v_1.z, v_2.z, v_3.z, location.z,
0, 0, 0, 1,
)
# fmt: on
return matrix
def get_placement(space):
"""Retrieve object placement"""
space_geom = ifcopenshell.geom.create_shape(BREP_SETTINGS, space)
# IfcOpenShell matrix values FreeCAD matrix values are transposed
ios_matrix = space_geom.transformation.matrix.data
m_l = list()
for i in range(3):
line = list(ios_matrix[i::3])
line[-1] *= SCALE
m_l.extend(line)
return FreeCAD.Matrix(*m_l)
def get_color(ifc_product):
"""Return a color depending on IfcClass given"""
product_colors = {
"IfcWall": (0.7, 0.3, 0.0),
"IfcWindow": (0.0, 0.7, 1.0),
"IfcSlab": (0.7, 0.7, 0.5),
"IfcRoof": (0.0, 0.3, 0.0),
"IfcDoor": (1.0, 1.0, 1.0),
}
for product, color in product_colors.items():
if ifc_product.is_a(product):
return color
else:
print(f"No color found for {ifc_product.is_a()}")
return (0.0, 0.0, 0.0)
def get_or_create_group(doc, name):
"""Get group by name or create one if not found"""
group = doc.findObjects("App::DocumentObjectGroup", name)
if group:
return group[0]
else:
return doc.addObject("App::DocumentObjectGroup", name)
def make_relspaceboundary(obj_name, ifc_entity=None):
"""Stantard FreeCAD FeaturePython Object creation method
ifc_entity : Optionnally provide a
"""
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", obj_name)
# ViewProviderRelSpaceBoundary(obj.ViewObject)
fpo = RelSpaceBoundary(obj)
obj.ViewObject.Proxy = 0
return obj
class RelSpaceBoundary:
"""Wrapping IFC entity : 
https://standards.buildingsmart.org/IFC/RELEASE/IFC4_1/FINAL/HTML/link/ifcrelspaceboundary2ndlevel.htm"""
def __init__(self, obj):
self.Type = "IfcRelSpaceBoundary"
obj.Proxy = self
category_name = "BEM"
ifc_attributes = "IFC Attributes"
obj.addProperty("App::PropertyString", "GlobalId", ifc_attributes)
obj.addProperty("App::PropertyLink", "RelatingSpace", ifc_attributes)
obj.addProperty("App::PropertyLink", "RelatedBuildingElement", ifc_attributes)
obj.addProperty(
"App::PropertyEnumeration", "PhysicalOrVirtualBoundary", ifc_attributes
).PhysicalOrVirtualBoundary = ["PHYSICAL", "VIRTUAL", "NOTDEFINED"]
obj.addProperty(
"App::PropertyEnumeration", "InternalOrExternalBoundary", ifc_attributes
).InternalOrExternalBoundary = [
"INTERNAL",
"EXTERNAL",
"EXTERNAL_EARTH",
"EXTERNAL_WATER",
"EXTERNAL_FIRE",
"NOTDEFINED",
]
obj.addProperty("App::PropertyLink", "CorrespondingBoundary", ifc_attributes)
obj.addProperty("App::PropertyLink", "ParentBoundary", ifc_attributes)
obj.addProperty("App::PropertyLinkList", "InnerBoundaries", ifc_attributes)
obj.addProperty("App::PropertyLink", "OriginalBoundary", category_name)
class Wall:
"""Wrapping IFC entity : 
https://standards.buildingsmart.org/IFC/RELEASE/IFC4_1/FINAL/HTML/link/chapter-1.htm"""
def __init__(self, obj):
self.Type = "IfcRelSpaceBoundary"
obj.Proxy = self
category_name = "BEM"
ifc_attributes = "IFC Attributes"
obj.addProperty("App::PropertyString", "GlobalId", ifc_attributes)
obj.addProperty("App::PropertyLink", "RelatingSpace", ifc_attributes)
obj.addProperty("App::PropertyLink", "RelatedBuildingElement", ifc_attributes)
obj.addProperty(
"App::PropertyEnumeration", "PhysicalOrVirtualBoundary", ifc_attributes
).PhysicalOrVirtualBoundary = ["PHYSICAL", "VIRTUAL", "NOTDEFINED"]
obj.addProperty(
"App::PropertyEnumeration", "InternalOrExternalBoundary", ifc_attributes
).InternalOrExternalBoundary = [
"INTERNAL",
"EXTERNAL",
"EXTERNAL_EARTH",
"EXTERNAL_WATER",
"EXTERNAL_FIRE",
"NOTDEFINED",
]
obj.addProperty("App::PropertyLink", "CorrespondingBoundary", ifc_attributes)
obj.addProperty("App::PropertyLink", "ParentBoundary", ifc_attributes)
obj.addProperty("App::PropertyLinkList", "InnerBoundaries", ifc_attributes)
obj.addProperty("App::PropertyLink", "OriginalBoundary", category_name)
def onChanged(self, fp, prop):
"""Do something when a property has changed"""
return
def execute(self, fp):
"""Do something when doing a recomputation, this method is mandatory"""
return
if __name__ == "__main__":
TEST_FOLDER = "/home/cyril/git/BIMxBEM/IfcTestFiles/"
TEST_FILES = [
"Triangle_R19.ifc",
"Triangle_ACAD.ifc",
"2Storey_ACAD.ifc",
"2Storey_R19.ifc",
]
IFC_PATH = os.path.join(TEST_FOLDER, TEST_FILES[0])
DOC = FreeCAD.ActiveDocument
if DOC: # Remote debugging
import ptvsd
# Allow other computers to attach to ptvsd at this IP address and port.
ptvsd.enable_attach(address=("localhost", 5678), redirect_output=True)
# Pause the program until a remote debugger is attached
ptvsd.wait_for_attach()
# breakpoint()
display_boundaries(ifc_path=IFC_PATH, doc=DOC)
FreeCADGui.activeView().viewIsometric()
FreeCADGui.SendMsgToActiveView("ViewFit")
else:
FreeCADGui.showMainWindow()
DOC = FreeCAD.newDocument()
display_boundaries(ifc_path=IFC_PATH, doc=DOC)
FreeCADGui.activeView().viewIsometric()
FreeCADGui.SendMsgToActiveView("ViewFit")
FreeCADGui.exec_loop()

Event Timeline