See #1676. You can now add basic space boundaries with the BIM Tool with Shift-B, either by selecting both a space and an element (e.g. wall) or selecting a space and placing the 3D cursor on a face.

This commit is contained in:
Dion Moult
2023-05-02 22:16:18 +10:00
parent a863c6d68e
commit 28686b2fa8
3 changed files with 120 additions and 2 deletions
@@ -20,6 +20,7 @@ import bpy
from . import ui, operator, prop from . import ui, operator, prop
classes = ( classes = (
operator.AddBoundary,
operator.ColourByRelatedBuildingElement, operator.ColourByRelatedBuildingElement,
operator.DisableEditingBoundary, operator.DisableEditingBoundary,
operator.DisableEditingBoundaryGeometry, operator.DisableEditingBoundaryGeometry,
@@ -20,11 +20,14 @@ import bpy
import bmesh import bmesh
import mathutils import mathutils
import logging import logging
import numpy as np
import ifcopenshell.api import ifcopenshell.api
import ifcopenshell.util.placement import ifcopenshell.util.placement
import ifcopenshell.util.unit import ifcopenshell.util.unit
import blenderbim.tool as tool import blenderbim.tool as tool
import blenderbim.bim.import_ifc as import_ifc import blenderbim.bim.import_ifc as import_ifc
from math import pi, inf
from mathutils import Vector
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.model.decorator import ProfileDecorator from blenderbim.bim.module.model.decorator import ProfileDecorator
from blenderbim.bim.module.boundary.decorator import BoundaryDecorator from blenderbim.bim.module.boundary.decorator import BoundaryDecorator
@@ -485,9 +488,9 @@ class ShowBoundaries(bpy.types.Operator, tool.Ifc.Operator):
continue continue
if tool.Ifc.is_moved(obj): if tool.Ifc.is_moved(obj):
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj) blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
element = tool.Ifc.get_entity(context.active_object) element = tool.Ifc.get_entity(obj)
for rel in element.BoundedBy or []: for rel in element.BoundedBy or []:
boundary_obj = loader.load_boundary(rel, context.active_object) boundary_obj = loader.load_boundary(rel, obj)
new = props.boundaries.add() new = props.boundaries.add()
new.obj = boundary_obj new.obj = boundary_obj
BoundaryDecorator.install(bpy.context) BoundaryDecorator.install(bpy.context)
@@ -521,3 +524,108 @@ class HideBoundaries(bpy.types.Operator, tool.Ifc.Operator):
bpy.data.objects.remove(boundary_obj) bpy.data.objects.remove(boundary_obj)
context.scene.BIMBoundaryProperties.boundaries.clear() context.scene.BIMBoundaryProperties.boundaries.clear()
return {"FINISHED"} return {"FINISHED"}
class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_boundary"
bl_label = "Add Boundary"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
relating_space = None
related_building_element = None
relating_space_obj = None
related_building_element_obj = None
objs = context.selected_objects
if len(objs) == 2:
# The user may select two objects, a space and its related building element
for obj in objs:
element = tool.Ifc.get_entity(obj)
if not element:
continue
if element.is_a("IfcSpace"):
relating_space = element
relating_space_obj = obj
else:
related_building_element = element
related_building_element_obj = obj
elif len(objs) == 1:
# Optionally the user may select just the space, and the building element shall be auto-detected
def msg(self, context):
self.layout.label(text="NO ACTIVE STOREY")
element = tool.Ifc.get_entity(objs[0])
if element.is_a("IfcSpace"):
relating_space = element
relating_space_obj = objs[0]
target = bpy.context.scene.cursor.location
collection = context.view_layer.active_layer_collection.collection
collection_obj = bpy.data.objects.get(collection.name)
if not collection_obj:
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
return
spatial_element = tool.Ifc.get_entity(collection_obj)
if not spatial_element:
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
return
for subelement in ifcopenshell.util.element.get_decomposition(spatial_element):
if not (subelement.is_a("IfcWall") or subelement.is_a("IfcSlab")):
continue
obj = tool.Ifc.get_object(subelement)
if obj:
raycast = obj.closest_point_on_mesh(obj.matrix_world.inverted() @ target, distance=0.1)
if raycast[0]:
related_building_element = subelement
related_building_element_obj = obj
break
if not relating_space or not related_building_element:
return
bm = bmesh.new()
bm.from_mesh(relating_space_obj.data)
bmesh.ops.dissolve_limit(bm, angle_limit=pi * 2 / 360, verts=bm.verts, edges=bm.edges)
target_distance = inf
target_face = None
for face in bm.faces:
centroid = relating_space_obj.matrix_world @ face.calc_center_median()
raycast = related_building_element_obj.closest_point_on_mesh(related_building_element_obj.matrix_world.inverted() @ centroid, distance=1)
if raycast[0]:
distance = (related_building_element_obj.matrix_world @ raycast[1] - centroid).length
if distance < target_distance:
target_face = face
target_distance = distance
if not target_face:
return
bm2 = bmesh.new()
bm2.faces.new([bm2.verts.new(v.co.copy()) for v in target_face.verts])
mesh = bpy.data.meshes.new("Boundary")
bm2.to_mesh(mesh)
bm2.free()
bm.free()
obj = bpy.data.objects.new("Boundary", mesh)
obj.matrix_world = relating_space_obj.matrix_world.copy()
bpy.context.scene.collection.objects.link(obj)
surface = tool.Model.export_surface(obj)
connection_geometry = tool.Ifc.get().createIfcConnectionSurfaceGeometry(surface)
boundary = tool.Ifc.run("root.create_entity", ifc_class="IfcRelSpaceBoundary")
boundary.RelatingSpace = relating_space
boundary.RelatedBuildingElement = related_building_element
boundary.ConnectionGeometry = connection_geometry
bpy.data.objects.remove(obj)
bpy.ops.bim.show_boundaries()
obj = tool.Ifc.get_object(boundary)
obj.select_set(True)
@@ -44,6 +44,7 @@ class BimTool(WorkSpaceTool):
# ("mesh.add_wall", {"type": 'LEFTMOUSE', "value": 'PRESS'}, {"properties": []}), # ("mesh.add_wall", {"type": 'LEFTMOUSE', "value": 'PRESS'}, {"properties": []}),
# ("bim.sync_modeling", {"type": 'MOUSEMOVE', "value": 'ANY'}, {"properties": []}), # ("bim.sync_modeling", {"type": 'MOUSEMOVE', "value": 'ANY'}, {"properties": []}),
("bim.hotkey", {"type": "A", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_A")]}), ("bim.hotkey", {"type": "A", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_A")]}),
("bim.hotkey", {"type": "B", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_B")]}),
("bim.hotkey", {"type": "C", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_C")]}), ("bim.hotkey", {"type": "C", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_C")]}),
("bim.hotkey", {"type": "E", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_E")]}), ("bim.hotkey", {"type": "E", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_E")]}),
("bim.hotkey", {"type": "F", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_F")]}), ("bim.hotkey", {"type": "F", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_F")]}),
@@ -261,6 +262,11 @@ class BimToolUI:
else: else:
row.operator("bim.add_potential_opening", text="Add Void") row.operator("bim.add_potential_opening", text="Add Void")
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_B")
row.operator("bim.add_boundary", text="Add Boundary")
if AuthoringData.data["is_voidable_element"]: if AuthoringData.data["is_voidable_element"]:
if AuthoringData.data["has_visible_openings"]: if AuthoringData.data["has_visible_openings"]:
row.operator("bim.edit_openings", icon="CHECKMARK", text="") row.operator("bim.edit_openings", icon="CHECKMARK", text="")
@@ -570,6 +576,9 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
elif self.active_material_usage == "PROFILE": elif self.active_material_usage == "PROFILE":
bpy.ops.bim.extend_profile(join_type="V") bpy.ops.bim.extend_profile(join_type="V")
def hotkey_S_B(self):
bpy.ops.bim.add_boundary()
def hotkey_S_O(self): def hotkey_S_O(self):
if len(bpy.context.selected_objects) == 2: if len(bpy.context.selected_objects) == 2:
bpy.ops.bim.add_opening() bpy.ops.bim.add_opening()