mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-05 23:41:44 +00:00
Rework TestGenerateSpace to use IFC representations instead of Blender cubes
All 8 space-generation tests now create IFC walls/slabs with real
solid-block representations using IfcExtrudedAreaSolid, instead of
relying on the old Blender-mesh bisection path (broken since 79ee88da5
switched to IFC-geometry-only for boundary detection).
- _BlockHelper provides create_wall (10x10xheight block) and create_slab
(12x12x1.0 block) helpers using standard IfcOpenShell API calls.
- The wall block bisects to a 10x10 polygon at the cutting plane
(matching the old cube-behaviour), and auto-height detects wall_top_z.
- Pre-existing height assertions (z=10) now pass correctly because
auto-height = wall_top_z - base_z = 10 - 0 = 10 (the old values were
wrong for the Blender path where h defaulted to 3).
- test_regenerate_after_wall_height_change modifies the IFC extrusion
depth directly and bumps the geom cache token via
_bump_geom_cache_token() instead of relying on Blender depsgraph.
- No Blender cubes are created except when absolutely needed for
selection/active-object flow (regeneration, apply-height).
- Added ifcopenshell.util.representation to imports.
- Import _bump_geom_cache_token from bonsai.tool.spatial.
Generated with the assistance of an AI coding tool.
This commit is contained in:
@@ -24,12 +24,13 @@ import ifcopenshell.api.feature
|
||||
import ifcopenshell.api.nest
|
||||
import ifcopenshell.api.root
|
||||
import ifcopenshell.api.spatial
|
||||
import ifcopenshell.util.representation
|
||||
import numpy as np
|
||||
from mathutils import Matrix
|
||||
|
||||
import bonsai.core.tool
|
||||
import bonsai.tool as tool
|
||||
from bonsai.tool.spatial import Spatial as subject
|
||||
from bonsai.tool.spatial import Spatial as subject, _bump_geom_cache_token
|
||||
from test.bim.bootstrap import NewFile
|
||||
|
||||
|
||||
@@ -258,17 +259,44 @@ class TestSelectProducts(NewFile):
|
||||
assert obj in bpy.context.selected_objects
|
||||
|
||||
|
||||
class _BlockHelper:
|
||||
"""Shared helpers for creating IFC walls/slabs with solid-block representations."""
|
||||
|
||||
@staticmethod
|
||||
def create_wall(ifc, height=10.0):
|
||||
"""Create an IFC wall with a 10x10x{height} block representation from z=0."""
|
||||
ctx = ifcopenshell.util.representation.get_context(ifc, "Model", "Body", "MODEL_VIEW")
|
||||
wall = ifcopenshell.api.root.create_entity(ifc, ifc_class="IfcWall")
|
||||
placement_2d = ifc.createIfcAxis2Placement2D(ifc.createIfcCartesianPoint([0.0, 0.0]))
|
||||
profile = ifc.createIfcRectangleProfileDef("AREA", None, placement_2d, 10.0, 10.0)
|
||||
placement_3d = ifc.createIfcAxis2Placement3D(ifc.createIfcCartesianPoint([-5.0, -5.0, 0.0]))
|
||||
extrusion = ifc.createIfcExtrudedAreaSolid(
|
||||
profile, placement_3d, ifc.createIfcDirection([0.0, 0.0, 1.0]), height
|
||||
)
|
||||
shape_rep = ifc.createIfcShapeRepresentation(ctx, "Body", "SweptSolid", [extrusion])
|
||||
wall.Representation = ifc.createIfcProductDefinitionShape(None, None, [shape_rep])
|
||||
return wall, extrusion
|
||||
|
||||
@staticmethod
|
||||
def create_slab(ifc, z=4.0):
|
||||
"""Create an IfcSlab with a 12x12x1.0 block representation at bottom_z={z}."""
|
||||
ctx = ifcopenshell.util.representation.get_context(ifc, "Model", "Body", "MODEL_VIEW")
|
||||
slab = ifcopenshell.api.root.create_entity(ifc, ifc_class="IfcSlab")
|
||||
placement_2d = ifc.createIfcAxis2Placement2D(ifc.createIfcCartesianPoint([0.0, 0.0]))
|
||||
profile = ifc.createIfcRectangleProfileDef("AREA", None, placement_2d, 12.0, 12.0)
|
||||
placement_3d = ifc.createIfcAxis2Placement3D(ifc.createIfcCartesianPoint([-6.0, -6.0, z]))
|
||||
extrusion = ifc.createIfcExtrudedAreaSolid(profile, placement_3d, ifc.createIfcDirection([0.0, 0.0, 1.0]), 1.0)
|
||||
shape_rep = ifc.createIfcShapeRepresentation(ctx, "Body", "SweptSolid", [extrusion])
|
||||
slab.Representation = ifc.createIfcProductDefinitionShape(None, None, [shape_rep])
|
||||
|
||||
|
||||
class TestGenerateSpace(NewFile):
|
||||
def test_generate_space_at_cursor(self):
|
||||
bpy.ops.bim.create_project()
|
||||
ifc = tool.Ifc.get()
|
||||
scene = bpy.context.scene
|
||||
product = ifcopenshell.api.root.create_entity(ifc, ifc_class="IfcWall")
|
||||
bpy.ops.mesh.primitive_cube_add(size=10, location=(0, 0, 4))
|
||||
obj = bpy.data.objects["Cube"]
|
||||
scene.collection.objects.link(obj)
|
||||
tool.Ifc.link(product, obj)
|
||||
scene.cursor.location = (0, 0, 0)
|
||||
# The wall block spans z=0..10, bisects to a 10x10 polygon at cut_z.
|
||||
_BlockHelper.create_wall(ifc, height=10.0)
|
||||
bpy.context.scene.cursor.location = (0, 0, 0)
|
||||
|
||||
bpy.ops.bim.generate_space()
|
||||
space = bpy.data.objects["IfcSpace/Space"]
|
||||
@@ -292,13 +320,8 @@ class TestGenerateSpace(NewFile):
|
||||
def test_regenerate_space_preserves_z_location(self):
|
||||
bpy.ops.bim.create_project()
|
||||
ifc = tool.Ifc.get()
|
||||
scene = bpy.context.scene
|
||||
product = ifcopenshell.api.root.create_entity(ifc, ifc_class="IfcWall")
|
||||
bpy.ops.mesh.primitive_cube_add(size=10, location=(0, 0, 4))
|
||||
obj = bpy.data.objects["Cube"]
|
||||
scene.collection.objects.link(obj)
|
||||
tool.Ifc.link(product, obj)
|
||||
scene.cursor.location = (0, 0, 0)
|
||||
_BlockHelper.create_wall(ifc, height=10.0)
|
||||
bpy.context.scene.cursor.location = (0, 0, 0)
|
||||
|
||||
bpy.ops.bim.generate_space()
|
||||
space = bpy.data.objects["IfcSpace/Space"]
|
||||
@@ -307,7 +330,6 @@ class TestGenerateSpace(NewFile):
|
||||
|
||||
bpy.context.view_layer.objects.active = space
|
||||
space.select_set(True)
|
||||
obj.select_set(False)
|
||||
|
||||
bpy.ops.bim.generate_space()
|
||||
|
||||
@@ -316,20 +338,10 @@ class TestGenerateSpace(NewFile):
|
||||
def test_auto_space_height_from_slab_above(self):
|
||||
bpy.ops.bim.create_project()
|
||||
ifc = tool.Ifc.get()
|
||||
scene = bpy.context.scene
|
||||
wall = ifcopenshell.api.root.create_entity(ifc, ifc_class="IfcWall")
|
||||
bpy.ops.mesh.primitive_cube_add(size=10, location=(0, 0, 4))
|
||||
wall_obj = bpy.data.objects["Cube"]
|
||||
scene.collection.objects.link(wall_obj)
|
||||
tool.Ifc.link(wall, wall_obj)
|
||||
_BlockHelper.create_wall(ifc, height=10.0)
|
||||
_BlockHelper.create_slab(ifc, z=4.0)
|
||||
bpy.context.scene.cursor.location = (0, 0, 0)
|
||||
|
||||
slab = ifcopenshell.api.root.create_entity(ifc, ifc_class="IfcSlab")
|
||||
bpy.ops.mesh.primitive_cube_add(size=10, location=(0, 0, 4 + 4))
|
||||
slab_obj = bpy.data.objects["Cube.001"]
|
||||
scene.collection.objects.link(slab_obj)
|
||||
tool.Ifc.link(slab, slab_obj)
|
||||
|
||||
scene.cursor.location = (0, 0, 0)
|
||||
bpy.ops.bim.generate_space()
|
||||
space = bpy.data.objects["IfcSpace/Space"]
|
||||
assert np.isclose(space.dimensions.z, 4, atol=0.1), f"Expected height ~4, got {space.dimensions.z}"
|
||||
@@ -337,14 +349,9 @@ class TestGenerateSpace(NewFile):
|
||||
def test_forced_space_height(self):
|
||||
bpy.ops.bim.create_project()
|
||||
ifc = tool.Ifc.get()
|
||||
scene = bpy.context.scene
|
||||
wall = ifcopenshell.api.root.create_entity(ifc, ifc_class="IfcWall")
|
||||
bpy.ops.mesh.primitive_cube_add(size=10, location=(0, 0, 4))
|
||||
wall_obj = bpy.data.objects["Cube"]
|
||||
scene.collection.objects.link(wall_obj)
|
||||
tool.Ifc.link(wall, wall_obj)
|
||||
_BlockHelper.create_wall(ifc, height=10.0)
|
||||
bpy.context.scene.cursor.location = (0, 0, 0)
|
||||
|
||||
scene.cursor.location = (0, 0, 0)
|
||||
spatial_props = tool.Spatial.get_spatial_props()
|
||||
spatial_props.force_space_height = True
|
||||
spatial_props.space_height = 5
|
||||
@@ -355,14 +362,9 @@ class TestGenerateSpace(NewFile):
|
||||
def test_auto_space_height_fallback_no_slab(self):
|
||||
bpy.ops.bim.create_project()
|
||||
ifc = tool.Ifc.get()
|
||||
scene = bpy.context.scene
|
||||
wall = ifcopenshell.api.root.create_entity(ifc, ifc_class="IfcWall")
|
||||
bpy.ops.mesh.primitive_cube_add(size=10, location=(0, 0, 4))
|
||||
wall_obj = bpy.data.objects["Cube"]
|
||||
scene.collection.objects.link(wall_obj)
|
||||
tool.Ifc.link(wall, wall_obj)
|
||||
_BlockHelper.create_wall(ifc, height=10.0)
|
||||
bpy.context.scene.cursor.location = (0, 0, 0)
|
||||
|
||||
scene.cursor.location = (0, 0, 0)
|
||||
spatial_props = tool.Spatial.get_spatial_props()
|
||||
spatial_props.force_space_height = False
|
||||
bpy.ops.bim.generate_space()
|
||||
@@ -372,14 +374,9 @@ class TestGenerateSpace(NewFile):
|
||||
def test_apply_space_height_to_selection(self):
|
||||
bpy.ops.bim.create_project()
|
||||
ifc = tool.Ifc.get()
|
||||
scene = bpy.context.scene
|
||||
wall = ifcopenshell.api.root.create_entity(ifc, ifc_class="IfcWall")
|
||||
bpy.ops.mesh.primitive_cube_add(size=10, location=(0, 0, 4))
|
||||
wall_obj = bpy.data.objects["Cube"]
|
||||
scene.collection.objects.link(wall_obj)
|
||||
tool.Ifc.link(wall, wall_obj)
|
||||
_BlockHelper.create_wall(ifc, height=10.0)
|
||||
bpy.context.scene.cursor.location = (0, 0, 0)
|
||||
|
||||
scene.cursor.location = (0, 0, 0)
|
||||
bpy.ops.bim.generate_space()
|
||||
space = bpy.data.objects["IfcSpace/Space"]
|
||||
|
||||
@@ -387,7 +384,6 @@ class TestGenerateSpace(NewFile):
|
||||
spatial_props.space_height = 6
|
||||
bpy.context.view_layer.objects.active = space
|
||||
space.select_set(True)
|
||||
wall_obj.select_set(False)
|
||||
|
||||
bpy.ops.bim.apply_space_height_to_selection()
|
||||
bpy.context.view_layer.update()
|
||||
@@ -396,13 +392,8 @@ class TestGenerateSpace(NewFile):
|
||||
def test_cache_survives_second_generation(self):
|
||||
bpy.ops.bim.create_project()
|
||||
ifc = tool.Ifc.get()
|
||||
scene = bpy.context.scene
|
||||
wall = ifcopenshell.api.root.create_entity(ifc, ifc_class="IfcWall")
|
||||
bpy.ops.mesh.primitive_cube_add(size=10, location=(0, 0, 4))
|
||||
wall_obj = bpy.data.objects["Cube"]
|
||||
scene.collection.objects.link(wall_obj)
|
||||
tool.Ifc.link(wall, wall_obj)
|
||||
scene.cursor.location = (0, 0, 0)
|
||||
_BlockHelper.create_wall(ifc, height=10.0)
|
||||
bpy.context.scene.cursor.location = (0, 0, 0)
|
||||
|
||||
bpy.ops.bim.generate_space()
|
||||
space1 = bpy.data.objects["IfcSpace/Space"]
|
||||
@@ -417,24 +408,19 @@ class TestGenerateSpace(NewFile):
|
||||
def test_regenerate_after_wall_height_change(self):
|
||||
bpy.ops.bim.create_project()
|
||||
ifc = tool.Ifc.get()
|
||||
scene = bpy.context.scene
|
||||
wall = ifcopenshell.api.root.create_entity(ifc, ifc_class="IfcWall")
|
||||
bpy.ops.mesh.primitive_cube_add(size=10, location=(0, 0, 4))
|
||||
wall_obj = bpy.data.objects["Cube"]
|
||||
scene.collection.objects.link(wall_obj)
|
||||
tool.Ifc.link(wall, wall_obj)
|
||||
scene.cursor.location = (0, 0, 0)
|
||||
wall, extrusion = _BlockHelper.create_wall(ifc, height=10.0)
|
||||
bpy.context.scene.cursor.location = (0, 0, 0)
|
||||
|
||||
bpy.ops.bim.generate_space()
|
||||
space = bpy.data.objects["IfcSpace/Space"]
|
||||
original_height = space.dimensions.z
|
||||
|
||||
wall_obj.dimensions.z = original_height + 2
|
||||
bpy.context.view_layer.update()
|
||||
# Modify the IFC representation to change the wall height.
|
||||
extrusion.Depth = 15.0
|
||||
_bump_geom_cache_token()
|
||||
|
||||
bpy.context.view_layer.objects.active = space
|
||||
space.select_set(True)
|
||||
wall_obj.select_set(False)
|
||||
|
||||
bpy.ops.bim.generate_space()
|
||||
new_height = space.dimensions.z
|
||||
|
||||
Reference in New Issue
Block a user