mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-19 06:39:13 +00:00
IfcRelSpaceBoundary: Create almost all boundaries for a selected space
* Refactor boundary creation to allow usage in a loop. * Loop through all building element in building storey instead of stopping to the first one * Enforce checking to prevent creation of invalid boundary geometries
This commit is contained in:
@@ -86,13 +86,25 @@ class Loader:
|
|||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
# Fallback solution for invalid geometry provided by Revit. (InnerBoundaries cuting OuterBoundary)
|
# Fallback solution for invalid geometry provided by Revit. (InnerBoundaries cuting OuterBoundary)
|
||||||
print(f"Failed to create mesh from IfcRelSpaceBoundary with ID {boundary.id()}. Geometry might be invalid")
|
print(f"Failed to create mesh from IfcRelSpaceBoundary with ID {boundary.id()}. Geometry might be invalid")
|
||||||
shape = ifcopenshell.geom.create_shape(self.fallback_settings, surface.OuterBoundary)
|
try:
|
||||||
|
shape = ifcopenshell.geom.create_shape(self.fallback_settings, surface.OuterBoundary)
|
||||||
|
except RuntimeError:
|
||||||
|
print(
|
||||||
|
f"Skipping IfcRelSpaceBoundary with ID {boundary.id()}. Fallback solution failed. Geometry might be invalid"
|
||||||
|
)
|
||||||
|
return None
|
||||||
mesh = bpy.data.meshes.new(str(surface.id()))
|
mesh = bpy.data.meshes.new(str(surface.id()))
|
||||||
bm = bmesh.new()
|
bm = bmesh.new()
|
||||||
verts = [bm.verts.new(shape.verts[i : i + 3]) for i in range(0, len(shape.verts), 3)]
|
verts = [bm.verts.new(shape.verts[i : i + 3]) for i in range(0, len(shape.verts), 3)]
|
||||||
bm.faces.new(verts)
|
bm.faces.new(verts)
|
||||||
for inner_boundary in surface.InnerBoundaries:
|
for inner_boundary in surface.InnerBoundaries:
|
||||||
shape = ifcopenshell.geom.create_shape(self.fallback_settings, inner_boundary)
|
try:
|
||||||
|
shape = ifcopenshell.geom.create_shape(self.fallback_settings, inner_boundary)
|
||||||
|
except RuntimeError:
|
||||||
|
print(
|
||||||
|
f"Skipping an inner boundary for IfcRelSpaceBoundary with ID {boundary.id()}. Geometry might be invalid"
|
||||||
|
)
|
||||||
|
return None
|
||||||
verts = [bm.verts.new(shape.verts[i : i + 3]) for i in range(0, len(shape.verts), 3)]
|
verts = [bm.verts.new(shape.verts[i : i + 3]) for i in range(0, len(shape.verts), 3)]
|
||||||
for i in range(len(verts) - 1):
|
for i in range(len(verts) - 1):
|
||||||
bm.edges.new(verts[i : i + 2])
|
bm.edges.new(verts[i : i + 2])
|
||||||
@@ -538,6 +550,7 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
related_building_element = None
|
related_building_element = None
|
||||||
relating_space_obj = None
|
relating_space_obj = None
|
||||||
related_building_element_obj = None
|
related_building_element_obj = None
|
||||||
|
parent_boundaries = []
|
||||||
|
|
||||||
objs = context.selected_objects
|
objs = context.selected_objects
|
||||||
if len(objs) == 2:
|
if len(objs) == 2:
|
||||||
@@ -552,6 +565,11 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
else:
|
else:
|
||||||
related_building_element = element
|
related_building_element = element
|
||||||
related_building_element_obj = obj
|
related_building_element_obj = obj
|
||||||
|
parent_boundary = self.create_element_boundary(
|
||||||
|
context, relating_space, relating_space_obj, related_building_element, related_building_element_obj
|
||||||
|
)
|
||||||
|
if parent_boundary:
|
||||||
|
parent_boundaries.append(parent_boundary)
|
||||||
elif len(objs) == 1:
|
elif len(objs) == 1:
|
||||||
# Optionally the user may select just the space, and the building element shall be auto-detected
|
# Optionally the user may select just the space, and the building element shall be auto-detected
|
||||||
# TODO : refactor to be able to generate all boundaries for selected space automatically or with an option
|
# TODO : refactor to be able to generate all boundaries for selected space automatically or with an option
|
||||||
@@ -564,8 +582,6 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
relating_space = element
|
relating_space = element
|
||||||
relating_space_obj = objs[0]
|
relating_space_obj = objs[0]
|
||||||
|
|
||||||
target = bpy.context.scene.cursor.location
|
|
||||||
|
|
||||||
collection = context.view_layer.active_layer_collection.collection
|
collection = context.view_layer.active_layer_collection.collection
|
||||||
collection_obj = bpy.data.objects.get(collection.name)
|
collection_obj = bpy.data.objects.get(collection.name)
|
||||||
if not collection_obj:
|
if not collection_obj:
|
||||||
@@ -583,12 +599,26 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
continue
|
continue
|
||||||
obj = tool.Ifc.get_object(subelement)
|
obj = tool.Ifc.get_object(subelement)
|
||||||
if obj:
|
if obj:
|
||||||
raycast = obj.closest_point_on_mesh(obj.matrix_world.inverted() @ target, distance=0.1)
|
related_building_element = subelement
|
||||||
if raycast[0]:
|
related_building_element_obj = obj
|
||||||
related_building_element = subelement
|
parent_boundary = self.create_element_boundary(
|
||||||
related_building_element_obj = obj
|
context,
|
||||||
break
|
relating_space,
|
||||||
|
relating_space_obj,
|
||||||
|
related_building_element,
|
||||||
|
related_building_element_obj,
|
||||||
|
)
|
||||||
|
if parent_boundary:
|
||||||
|
parent_boundaries.append(parent_boundary)
|
||||||
|
|
||||||
|
bpy.ops.bim.show_boundaries()
|
||||||
|
for parent_boundary in parent_boundaries:
|
||||||
|
obj = tool.Ifc.get_object(parent_boundary)
|
||||||
|
obj.select_set(True)
|
||||||
|
|
||||||
|
def create_element_boundary(
|
||||||
|
self, context, relating_space, relating_space_obj, related_building_element, related_building_element_obj
|
||||||
|
):
|
||||||
if not relating_space or not related_building_element:
|
if not relating_space or not related_building_element:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -614,8 +644,6 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
if not target_face:
|
if not target_face:
|
||||||
return
|
return
|
||||||
|
|
||||||
parent_boundary = tool.Ifc.run("root.create_entity", ifc_class=context.scene.BIMModelProperties.boundary_class)
|
|
||||||
|
|
||||||
# Is this right? Or should I use loop?
|
# Is this right? Or should I use loop?
|
||||||
target_face_verts = [v.co.copy() for v in target_face.verts]
|
target_face_verts = [v.co.copy() for v in target_face.verts]
|
||||||
target_face_matrix = self.get_face_matrix(*[v.copy() for v in target_face_verts[0:3]])
|
target_face_matrix = self.get_face_matrix(*[v.copy() for v in target_face_verts[0:3]])
|
||||||
@@ -635,6 +663,18 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
gross_boundary_polygon = geom
|
gross_boundary_polygon = geom
|
||||||
break
|
break
|
||||||
|
|
||||||
|
if (
|
||||||
|
not (isinstance(gross_boundary_polygon, shapely.Polygon) and gross_boundary_polygon.is_valid)
|
||||||
|
or gross_boundary_polygon.is_empty
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
parent_boundary = tool.Ifc.run("root.create_entity", ifc_class=context.scene.BIMModelProperties.boundary_class)
|
||||||
|
parent_boundary.PhysicalOrVirtualBoundary = "PHYSICAL"
|
||||||
|
# Set to EXTERNAL by default and turn later to internal if there is a corresponding boundary relating to an
|
||||||
|
# internal space
|
||||||
|
parent_boundary.InternalOrExternalBoundary = "EXTERNAL"
|
||||||
|
|
||||||
# The gross boundary polygon may not be a true gross boundary since it
|
# The gross boundary polygon may not be a true gross boundary since it
|
||||||
# may have openings already removed, such as in IFC4 Reference View. So
|
# may have openings already removed, such as in IFC4 Reference View. So
|
||||||
# we cheat by using the exterior boundary to mean "gross". Later, we
|
# we cheat by using the exterior boundary to mean "gross". Later, we
|
||||||
@@ -674,7 +714,7 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
boundary.PhysicalOrVirtualBoundary = "VIRTUAL"
|
boundary.PhysicalOrVirtualBoundary = "VIRTUAL"
|
||||||
else:
|
else:
|
||||||
boundary.PhysicalOrVirtualBoundary = "PHYSICAL"
|
boundary.PhysicalOrVirtualBoundary = "PHYSICAL"
|
||||||
boundary.InternalOrExternalBoundary = "INTERNAL"
|
boundary.InternalOrExternalBoundary = parent_boundary.InternalOrExternalBoundary
|
||||||
self.set_boundary_name(boundary)
|
self.set_boundary_name(boundary)
|
||||||
if boundary.is_a("IfcRelSpaceBoundary2ndLevel"):
|
if boundary.is_a("IfcRelSpaceBoundary2ndLevel"):
|
||||||
boundary.ParentBoundary = parent_boundary
|
boundary.ParentBoundary = parent_boundary
|
||||||
@@ -685,13 +725,8 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
parent_boundary.RelatingSpace = relating_space
|
parent_boundary.RelatingSpace = relating_space
|
||||||
parent_boundary.RelatedBuildingElement = related_building_element
|
parent_boundary.RelatedBuildingElement = related_building_element
|
||||||
parent_boundary.ConnectionGeometry = connection_geometry
|
parent_boundary.ConnectionGeometry = connection_geometry
|
||||||
parent_boundary.PhysicalOrVirtualBoundary = "PHYSICAL"
|
|
||||||
parent_boundary.InternalOrExternalBoundary = "INTERNAL"
|
|
||||||
self.set_boundary_name(parent_boundary)
|
self.set_boundary_name(parent_boundary)
|
||||||
|
return parent_boundary
|
||||||
bpy.ops.bim.show_boundaries()
|
|
||||||
obj = tool.Ifc.get_object(parent_boundary)
|
|
||||||
obj.select_set(True)
|
|
||||||
|
|
||||||
def get_face_matrix(self, p1, p2, p3):
|
def get_face_matrix(self, p1, p2, p3):
|
||||||
edge1 = p2 - p1
|
edge1 = p2 - p1
|
||||||
|
|||||||
Reference in New Issue
Block a user