From b13cbbb244f5e2624421ab45f7deecb254f79bd8 Mon Sep 17 00:00:00 2001 From: Massimo Fabbro Date: Fri, 15 Sep 2023 22:47:35 +0200 Subject: [PATCH] Partial spatial tool refactor --- .../blenderbim/bim/module/model/__init__.py | 1 + .../blenderbim/bim/module/model/space.py | 218 ++---------------- src/blenderbim/blenderbim/core/spatial.py | 66 ++++++ src/blenderbim/blenderbim/core/tool.py | 13 ++ src/blenderbim/blenderbim/tool/spatial.py | 171 +++++++++++++- .../test/bim/feature/spatial.feature | 57 +++-- 6 files changed, 317 insertions(+), 209 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/model/__init__.py b/src/blenderbim/blenderbim/bim/module/model/__init__.py index bdb5f5fe01..8220be3df9 100644 --- a/src/blenderbim/blenderbim/bim/module/model/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/model/__init__.py @@ -106,6 +106,7 @@ classes = ( slab.SetArcIndex, space.GenerateSpace, space.GenerateSpacesFromWalls, + space.GenerateFlooringCoveringsFromWalls, space.ToggleSpaceVisibility, mep.FitFlowSegments, mep.RegenerateDistributionElement, diff --git a/src/blenderbim/blenderbim/bim/module/model/space.py b/src/blenderbim/blenderbim/bim/module/model/space.py index 2af8ae020c..efbcd9bdca 100644 --- a/src/blenderbim/blenderbim/bim/module/model/space.py +++ b/src/blenderbim/blenderbim/bim/module/model/space.py @@ -23,6 +23,7 @@ import shapely import ifcopenshell import ifcopenshell.util.element import blenderbim.tool as tool +import blenderbim.core.spatial as core import blenderbim.core.type from math import pi from mathutils import Vector, Matrix @@ -230,182 +231,29 @@ class GenerateSpacesFromWalls(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): # This only works based on a 2D plan only considering the standard # walls (i.e. prismatic) in the active object storey. - # In order to run, the active objct must be a wall and - # have to be selected walls - props = context.scene.BIMModelProperties + # In order to run, the active object must be a wall and + # there must be selected walls + core.generate_spaces_from_walls(tool.Ifc, tool.Spatial, tool.Collector) + +class GenerateFlooringCoveringsFromWalls(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.generate_flooring_coverings_from_walls" + bl_label = "Generate Flooring Coverings From Walls" + bl_options = {"REGISTER", "UNDO"} + bl_description = "Generate flooring coverings from selected walls. The active object must be a wall" + + @classmethod + def poll(cls, context): active_obj = bpy.context.active_object - - if not active_obj: - self.report({"ERROR"}, "No active object. Please select a wall") - return - element = tool.Ifc.get_entity(active_obj) - if element and not element.is_a("IfcWall"): - return self.report({"ERROR"}, "The active object is not a wall. Please select a wall.") - - container = ifcopenshell.util.element.get_container(element) - if not container: - self.report({"ERROR"}, "The wall is not contained.") - - if not bpy.context.selected_objects: - self.report({"ERROR"}, "No selected objects found. Please select walls.") - return - - x, y, z = active_obj.matrix_world.translation.xyz - mat = active_obj.matrix_world - h = active_obj.dimensions.z - selected_objects = bpy.context.selected_objects - - boundary_elements = self.get_boundary_elements(selected_objects) - - polys = self.get_polygons(boundary_elements) - - converted_tolerance = self.get_converted_tolerance(tolerance=0.03) - - union = shapely.ops.unary_union(polys).buffer(converted_tolerance, cap_style=2, join_style=2) - - union = self.get_purged_inner_holes_poly(union_geom=union, min_area=self.get_converted_tolerance(tolerance=3)) - - for i, linear_ring in enumerate(union.interiors): - poly = Polygon(linear_ring) - poly = poly.buffer(converted_tolerance, single_sided=True, cap_style=2, join_style=2) - - bm = self.get_bmesh_from_polygon(poly, mat, h) - - name = "Space" + str(i) - mesh = bpy.data.meshes.new(name=name) - bm.to_mesh(mesh) - bm.free() - - obj = bpy.data.objects.new(name, mesh) - obj.matrix_world = mat - - self.set_obj_origin_to_bboxcenter(obj) - - if z != 0: - obj.location = obj.location + Vector((0, 0, z)) - - context.view_layer.active_layer_collection.collection.objects.link(obj) - bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcSpace") - container_obj = tool.Ifc.get_object(container) - blenderbim.core.spatial.assign_container( - tool.Ifc, tool.Collector, tool.Spatial, structure_obj=container_obj, element_obj=obj - ) - - def get_boundary_elements(self, selected_objects): - boundary_elements = [] - for obj in selected_objects: - subelement = tool.Ifc.get_entity(obj) - if subelement.is_a("IfcWall") or subelement.is_a("IfcColumn"): - boundary_elements.append(subelement) - return boundary_elements - - def get_polygons(self, boundary_elements): - polys = [] - for boundary_element in boundary_elements: - obj = tool.Ifc.get_object(boundary_element) - if not obj: - continue - points = [] - base = self.get_obj_base_points(obj) - for index in ["low_left", "low_right", "high_right", "high_left"]: - point = base[index] - points.append(point) - - polys.append(Polygon(points)) - return polys - - def get_obj_base_points(self, obj): - x_values = [(obj.matrix_world @ Vector(v)).x for v in obj.bound_box] - y_values = [(obj.matrix_world @ Vector(v)).y for v in obj.bound_box] - return { - "low_left": (x_values[0], y_values[0]), - "high_left": (x_values[3], y_values[3]), - "low_right": (x_values[4], y_values[4]), - "high_right": (x_values[7], y_values[7]), - } - - def get_converted_tolerance(self, tolerance): - model = tool.Ifc.get() - project_unit = ifcopenshell.util.unit.get_project_unit(model, "LENGTHUNIT") - prefix = getattr(project_unit, "Prefix", None) - - converted_tolerance = ifcopenshell.util.unit.convert( - value=tolerance, - from_prefix=None, - from_unit="METRE", - to_prefix=prefix, - to_unit=project_unit.Name, - ) - return tolerance - - def get_purged_inner_holes_poly(self, union_geom, min_area): - interiors_list = [] - - if union_geom.geom_type == "MultiPolygon": - for poly in union_geom.geoms: - interiors_list = self.get_poly_valid_interior_list( - poly=poly, min_area=min_area, interiors_list=interiors_list - ) - - new_poly = Polygon(poly.exterior.coords, holes=interiors_list) - - if union_geom.geom_type == "Polygon": - interiors_list = self.get_poly_valid_interior_list( - poly=union_geom, min_area=min_area, interiors_list=interiors_list - ) - new_poly = Polygon(union_geom.exterior.coords, holes=interiors_list) - - return new_poly - - def get_poly_valid_interior_list(self, poly, min_area, interiors_list): - for interior in poly.interiors: - p = Polygon(interior) - if p.area >= min_area: - interiors_list.append(interior) - return interiors_list - - def get_bmesh_from_polygon(self, poly, mat, h): - bm = bmesh.new() - bm.verts.index_update() - bm.edges.index_update() - - mat_invert = mat.inverted() - - new_verts = [bm.verts.new(mat_invert @ Vector([v[0], v[1], 0])) for v in poly.exterior.coords[0:-1]] - [bm.edges.new((new_verts[i], new_verts[i + 1])) for i in range(len(new_verts) - 1)] - bm.edges.new((new_verts[len(new_verts) - 1], new_verts[0])) - - bm.verts.index_update() - bm.edges.index_update() - - bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=1e-5) - bmesh.ops.triangle_fill(bm, edges=bm.edges) - bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 5, verts=bm.verts, edges=bm.edges) - - extrusion = bmesh.ops.extrude_face_region(bm, geom=bm.faces) - extruded_verts = [g for g in extrusion["geom"] if isinstance(g, bmesh.types.BMVert)] - bmesh.ops.translate(bm, vec=[0.0, 0.0, h], verts=extruded_verts) - - bmesh.ops.recalc_face_normals(bm, faces=bm.faces) - - return bm - - def set_obj_origin_to_bboxcenter(self, obj): - mat = obj.matrix_world - inverted = mat.inverted() - local_bbox_center = 0.125 * sum((Vector(b) for b in obj.bound_box), Vector()) - global_bbox_center = mat @ local_bbox_center - - oldLoc = obj.location - newLoc = global_bbox_center - diff = newLoc - oldLoc - for vert in obj.data.vertices: - aux_vector = mat @ vert.co - aux_vector = aux_vector - diff - vert.co = inverted @ aux_vector - obj.location = newLoc + if element: + return context.selected_objects and element.is_a("IfcWall") + def _execute(self, context): + # This only works based on a 2D plan only considering the standard + # walls (i.e. prismatic) in the active object storey. + # In order to run, the active object must be a wall and + # there must be selected walls + core.generate_flooring_coverings_from_walls(tool.Ifc, tool.Spatial, tool.Collector) class ToggleSpaceVisibility(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.toggle_space_visibility" @@ -414,26 +262,6 @@ class ToggleSpaceVisibility(bpy.types.Operator, tool.Ifc.Operator): bl_description = "Change the space visibility" def execute(cls, context): - model = tool.Ifc.get() + core.toggle_space_visibility(tool.Ifc, tool.Spatial) + return {"FINISHED"} - spaces = model.by_type("IfcSpace") - - if not spaces: - print(spaces) - return {"FINISHED"} - - first_obj = tool.Ifc.get_object(spaces[0]) - - if bpy.data.objects[first_obj.name].display_type == "TEXTURED": - for space in spaces: - obj = tool.Ifc.get_object(space) - bpy.data.objects[obj.name].show_wire = True - bpy.data.objects[obj.name].display_type = "WIRE" - return {"FINISHED"} - - elif bpy.data.objects[first_obj.name].display_type == "WIRE": - for space in spaces: - obj = tool.Ifc.get_object(space) - bpy.data.objects[obj.name].show_wire = False - bpy.data.objects[obj.name].display_type = "TEXTURED" - return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/core/spatial.py b/src/blenderbim/blenderbim/core/spatial.py index fb75dd2671..7864ad0845 100644 --- a/src/blenderbim/blenderbim/core/spatial.py +++ b/src/blenderbim/blenderbim/core/spatial.py @@ -17,6 +17,8 @@ # along with BlenderBIM Add-on. If not, see . import blenderbim.core +import bpy +import ifcopenshell def reference_structure(ifc, spatial, structure=None, element=None): @@ -120,3 +122,67 @@ def select_decomposed_elements(spatial): container = spatial.get_active_container() if container: spatial.select_products(spatial.get_decomposed_elements(container)) + +#HERE STARTS SPATIAL TOOL + +def generate_spaces_from_walls(ifc, spatial, collector): + #props = bpy.context.scene.BIMModelProperties + container, active_obj = spatial.get_container_and_active_obj() + + if not active_obj: + self.report({"ERROR"}, "No active object. Please select a wall") + return + + element = ifc.get_entity(active_obj) + if element and not element.is_a("IfcWall"): + return self.report({"ERROR"}, "The active object is not a wall. Please select a wall.") + + if not container: + self.report({"ERROR"}, "The wall is not contained.") + + if not bpy.context.selected_objects: + self.report({"ERROR"}, "No selected objects found. Please select walls.") + return + + x, y, z = active_obj.matrix_world.translation.xyz + mat = active_obj.matrix_world + h = active_obj.dimensions.z + selected_objects = bpy.context.selected_objects + + union = spatial.get_union_shape_from_selected_objects(selected_objects) + + for i, linear_ring in enumerate(union.interiors): + poly = spatial.get_buffered_poly_from_linear_ring(linear_ring) + + bm = spatial.get_bmesh_from_polygon(poly, mat, h) + + name = "Space" + str(i) + mesh = bpy.data.meshes.new(name=name) + bm.to_mesh(mesh) + bm.free() + + obj = bpy.data.objects.new(name, mesh) + obj.matrix_world = mat + + spatial.set_obj_origin_to_bboxcenter(obj) + + if z != 0: + obj.location = obj.location + Vector((0, 0, z)) + + bpy.context.view_layer.active_layer_collection.collection.objects.link(obj) + bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcSpace") + container_obj = ifc.get_object(container) + blenderbim.core.spatial.assign_container( + ifc, collector, spatial, structure_obj=container_obj, element_obj=obj + ) + +def generate_flooring_coverings_from_walls(ifc, spatial, collector): + pass + +def toggle_space_visibility(ifc, spatial): + model = ifc.get() + spaces = model.by_type("IfcSpace") + if not spaces: + return + spatial.toggle_spaces_visibility_wired_and_textured(spaces) + diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index f0f168a7ba..b249df13f3 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -810,6 +810,19 @@ class Spatial: def set_active_object(cls, obj): pass def set_relative_object_matrix(cls, target_obj, relative_to_obj, matrix): pass def show_scene_objects(cls): pass + #HERE STARTS SPATIAL TOOL + def get_container_and_active_obj(cls): pass + def get_union_shape_from_selected_objects(cls, selected_objects): pass + def get_boundary_elements(cls, selected_objects): pass + def get_polygons(cls, boundary_elements): pass + def get_obj_base_points(cls, obj): pass + def get_converted_tolerance(cls, tolerance): pass + def get_purged_inner_holes_poly(cls, union_geom, min_area): pass + def get_poly_valid_interior_list(cls, poly, min_area, interiors_list): pass + def get_buffered_poly_from_linear_ring(cls, linear_ring): pass + def get_bmesh_from_polygon(cls, poly, mat, h): pass + def set_obj_origin_to_bboxcenter(cls, obj): pass + def toggle_spaces_visibility_wired_and_textured(cls, spaces): pass @interface diff --git a/src/blenderbim/blenderbim/tool/spatial.py b/src/blenderbim/blenderbim/tool/spatial.py index 5c3b6b895a..c6865c9c1d 100644 --- a/src/blenderbim/blenderbim/tool/spatial.py +++ b/src/blenderbim/blenderbim/tool/spatial.py @@ -17,13 +17,17 @@ # along with BlenderBIM Add-on. If not, see . import bpy +import bmesh +import shapely import ifcopenshell import blenderbim.core.tool import blenderbim.core.root import blenderbim.core.spatial import blenderbim.tool as tool import json - +from math import pi +from mathutils import Vector, Matrix +from shapely import Polygon, MultiPolygon class Spatial(blenderbim.core.tool.Spatial): @classmethod @@ -262,3 +266,168 @@ class Spatial(blenderbim.core.tool.Spatial): contracted_containers = json.loads(props.contracted_containers) contracted_containers.remove(container.id()) props.contracted_containers = json.dumps(contracted_containers) + +#HERE STARTS SPATIAL TOOL + + @classmethod + def get_container_and_active_obj(cls): + active_obj = bpy.context.active_object + element = tool.Ifc.get_entity(active_obj) + container = ifcopenshell.util.element.get_container(element) + return container, active_obj + + @classmethod + def get_union_shape_from_selected_objects(cls, selected_objects): + boundary_elements = cls.get_boundary_elements(selected_objects) + polys = cls.get_polygons(boundary_elements) + converted_tolerance = cls.get_converted_tolerance(tolerance=0.03) + union = shapely.ops.unary_union(polys).buffer(converted_tolerance, cap_style=2, join_style=2) + union = cls.get_purged_inner_holes_poly(union_geom=union, min_area=cls.get_converted_tolerance(tolerance=3)) + + return union + + @classmethod + def get_boundary_elements(cls, selected_objects): + boundary_elements = [] + for obj in selected_objects: + subelement = tool.Ifc.get_entity(obj) + if subelement.is_a("IfcWall") or subelement.is_a("IfcColumn"): + boundary_elements.append(subelement) + return boundary_elements + + @classmethod + def get_polygons(cls, boundary_elements): + polys = [] + for boundary_element in boundary_elements: + obj = tool.Ifc.get_object(boundary_element) + if not obj: + continue + points = [] + base = cls.get_obj_base_points(obj) + for index in ["low_left", "low_right", "high_right", "high_left"]: + point = base[index] + points.append(point) + + polys.append(Polygon(points)) + return polys + + @classmethod + def get_obj_base_points(cls, obj): + x_values = [(obj.matrix_world @ Vector(v)).x for v in obj.bound_box] + y_values = [(obj.matrix_world @ Vector(v)).y for v in obj.bound_box] + return { + "low_left": (x_values[0], y_values[0]), + "high_left": (x_values[3], y_values[3]), + "low_right": (x_values[4], y_values[4]), + "high_right": (x_values[7], y_values[7]), + } + + @classmethod + def get_converted_tolerance(cls, tolerance): + model = tool.Ifc.get() + project_unit = ifcopenshell.util.unit.get_project_unit(model, "LENGTHUNIT") + prefix = getattr(project_unit, "Prefix", None) + + converted_tolerance = ifcopenshell.util.unit.convert( + value=tolerance, + from_prefix=None, + from_unit="METRE", + to_prefix=prefix, + to_unit=project_unit.Name, + ) + return tolerance + + @classmethod + def get_purged_inner_holes_poly(cls, union_geom, min_area): + interiors_list = [] + + if union_geom.geom_type == "MultiPolygon": + for poly in union_geom.geoms: + interiors_list = cls.get_poly_valid_interior_list( + poly=poly, min_area=min_area, interiors_list=interiors_list + ) + + new_poly = Polygon(poly.exterior.coords, holes=interiors_list) + + if union_geom.geom_type == "Polygon": + interiors_list = cls.get_poly_valid_interior_list( + poly=union_geom, min_area=min_area, interiors_list=interiors_list + ) + new_poly = Polygon(union_geom.exterior.coords, holes=interiors_list) + + return new_poly + + @classmethod + def get_poly_valid_interior_list(cls, poly, min_area, interiors_list): + for interior in poly.interiors: + p = Polygon(interior) + if p.area >= min_area: + interiors_list.append(interior) + return interiors_list + + @classmethod + def get_buffered_poly_from_linear_ring(cls, linear_ring): + poly = Polygon(linear_ring) + converted_tolerance = cls.get_converted_tolerance(tolerance=0.03) + poly = poly.buffer(converted_tolerance, single_sided=True, cap_style=2, join_style=2) + return poly + + @classmethod + def get_bmesh_from_polygon(cls, poly, mat, h): + bm = bmesh.new() + bm.verts.index_update() + bm.edges.index_update() + + mat_invert = mat.inverted() + + new_verts = [bm.verts.new(mat_invert @ Vector([v[0], v[1], 0])) for v in poly.exterior.coords[0:-1]] + [bm.edges.new((new_verts[i], new_verts[i + 1])) for i in range(len(new_verts) - 1)] + bm.edges.new((new_verts[len(new_verts) - 1], new_verts[0])) + + bm.verts.index_update() + bm.edges.index_update() + + bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=1e-5) + bmesh.ops.triangle_fill(bm, edges=bm.edges) + bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 5, verts=bm.verts, edges=bm.edges) + + extrusion = bmesh.ops.extrude_face_region(bm, geom=bm.faces) + extruded_verts = [g for g in extrusion["geom"] if isinstance(g, bmesh.types.BMVert)] + bmesh.ops.translate(bm, vec=[0.0, 0.0, h], verts=extruded_verts) + + bmesh.ops.recalc_face_normals(bm, faces=bm.faces) + + return bm + + @classmethod + def set_obj_origin_to_bboxcenter(cls, obj): + mat = obj.matrix_world + inverted = mat.inverted() + local_bbox_center = 0.125 * sum((Vector(b) for b in obj.bound_box), Vector()) + global_bbox_center = mat @ local_bbox_center + + oldLoc = obj.location + newLoc = global_bbox_center + diff = newLoc - oldLoc + for vert in obj.data.vertices: + aux_vector = mat @ vert.co + aux_vector = aux_vector - diff + vert.co = inverted @ aux_vector + obj.location = newLoc + + @classmethod + def toggle_spaces_visibility_wired_and_textured(cls, spaces): + first_obj = tool.Ifc.get_object(spaces[0]) + if bpy.data.objects[first_obj.name].display_type == "TEXTURED": + for space in spaces: + obj = tool.Ifc.get_object(space) + bpy.data.objects[obj.name].show_wire = True + bpy.data.objects[obj.name].display_type = "WIRE" + return + + elif bpy.data.objects[first_obj.name].display_type == "WIRE": + for space in spaces: + obj = tool.Ifc.get_object(space) + bpy.data.objects[obj.name].show_wire = False + bpy.data.objects[obj.name].display_type = "TEXTURED" + return diff --git a/src/blenderbim/test/bim/feature/spatial.feature b/src/blenderbim/test/bim/feature/spatial.feature index acdf6ee292..4e0d84d88c 100644 --- a/src/blenderbim/test/bim/feature/spatial.feature +++ b/src/blenderbim/test/bim/feature/spatial.feature @@ -1,6 +1,6 @@ @spatial Feature: Spatial - Covers spatial containment management. + Covers spatial containment management and spatial tool. Scenario: Enable editing container Given an empty IFC project @@ -21,8 +21,7 @@ Scenario: Assign container Given an empty IFC project And I add a cube And the object "Cube" is selected - And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" - And I press "bim.assign_class" + And I press "bim.assign_class(ifc_class='IfcWall', predefined_type='SOLIDWALL')" And the object "IfcWall/Cube" is selected And I press "bim.enable_editing_container" And the variable "site" is "tool.Ifc.get().by_type('IfcSite')[0].id()" @@ -33,8 +32,7 @@ Scenario: Copy to container Given an empty IFC project And I add a cube And the object "Cube" is selected - And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" - And I press "bim.assign_class" + And I press "bim.assign_class(ifc_class='IfcWall', predefined_type='SOLIDWALL')" And the object "IfcWall/Cube" is selected And I press "bim.enable_editing_container" When I set "scene.BIMSpatialProperties.containers[0].is_selected" to "True" @@ -45,8 +43,7 @@ Scenario: Reference structure Given an empty IFC project And I add a cube And the object "Cube" is selected - And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" - And I press "bim.assign_class" + And I press "bim.assign_class(ifc_class='IfcWall', predefined_type='SOLIDWALL')" And the object "IfcWall/Cube" is selected And I press "bim.enable_editing_container" When I set "scene.BIMSpatialProperties.containers[0].is_selected" to "True" @@ -57,8 +54,7 @@ Scenario: Dereference structure Given an empty IFC project And I add a cube And the object "Cube" is selected - And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" - And I press "bim.assign_class" + And I press "bim.assign_class(ifc_class='IfcWall', predefined_type='SOLIDWALL')" And the object "IfcWall/Cube" is selected And I press "bim.enable_editing_container" When I set "scene.BIMSpatialProperties.containers[0].is_selected" to "True" @@ -70,8 +66,7 @@ Scenario: Select container Given an empty IFC project And I add a cube And the object "Cube" is selected - And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" - And I press "bim.assign_class" + And I press "bim.assign_class(ifc_class='IfcWall', predefined_type='SOLIDWALL')" And the object "IfcWall/Cube" is selected And I press "bim.enable_editing_container" And the variable "site" is "tool.Ifc.get().by_type('IfcSite')[0].id()" @@ -83,11 +78,47 @@ Scenario: Select similar container Given an empty IFC project And I add a cube And the object "Cube" is selected - And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" - And I press "bim.assign_class" + And I press "bim.assign_class(ifc_class='IfcWall', predefined_type='SOLIDWALL')" And the object "IfcWall/Cube" is selected And I press "bim.enable_editing_container" And the variable "site" is "tool.Ifc.get().by_type('IfcSite')[0].id()" And I press "bim.assign_container(structure={site})" When I press "bim.select_similar_container" Then nothing happens + +#HERE STARTS TESTS FOR SPATIAL TOOL + +Scenario: Execute generate space from cursor position + Given an empty IFC project + When I press "bim.generate_space" + Then nothing happens + +Scenario: Execute generate spaces from walls + Given an empty IFC project + And I load the demo construction library + And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType" + And the variable "element_type" is "[e for e in {ifc}.by_type('IfcWallType') if e.Name == 'WAL100'][0].id()" + And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}" + And I press "bim.add_constr_type_instance" + And the object "IfcWall/Wall" is selected + When I press "bim.generate_spaces_from_walls" + Then nothing happens + +Scenario: Execute generate flooring coverings from walls + Given an empty IFC project + And I load the demo construction library + And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType" + And the variable "element_type" is "[e for e in {ifc}.by_type('IfcWallType') if e.Name == 'WAL100'][0].id()" + And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}" + And I press "bim.add_constr_type_instance" + And the object "IfcWall/Wall" is selected + When I press "bim.generate_flooring_coverings_from_walls" + Then nothing happens + +Scenario: Execute toggle space visibility + Given an empty IFC project + And I add a cube + And the object "Cube" is selected + And I press "bim.assign_class(ifc_class='IfcSpace', predefined_type='SPACE')" + When I press "bim.toggle_space_visibility" + Then nothing happens