mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
See #6501. Fix bug where wall alignment didn't strictly check for poll requirements prior to executing operator.
This commit is contained in:
@@ -577,49 +577,12 @@ class AlignProduct(bpy.types.Operator):
|
|||||||
align_type: AlignType
|
align_type: AlignType
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
selected_objs = context.selected_objects
|
try:
|
||||||
if len(selected_objs) < 2 or not context.active_object:
|
core.align_objects(tool.Blender, tool.Model, self.align_type)
|
||||||
self.report({"ERROR"}, "Please select atleast 2 objects.")
|
except core.RequireAtLeastTwoElements as e:
|
||||||
return {"FINISHED"}
|
self.report({"ERROR"}, str(e))
|
||||||
if self.align_type == "CENTERLINE":
|
|
||||||
point = context.active_object.matrix_world @ (
|
|
||||||
Vector(context.active_object.bound_box[0]) + (context.active_object.dimensions / 2)
|
|
||||||
)
|
|
||||||
elif self.align_type == "POSITIVE":
|
|
||||||
point = context.active_object.matrix_world @ Vector(context.active_object.bound_box[6])
|
|
||||||
elif self.align_type == "NEGATIVE":
|
|
||||||
point = context.active_object.matrix_world @ Vector(context.active_object.bound_box[0])
|
|
||||||
else:
|
|
||||||
assert_never(self.align_type)
|
|
||||||
|
|
||||||
active_x_axis = context.active_object.matrix_world.to_quaternion() @ Vector((1, 0, 0))
|
|
||||||
active_y_axis = context.active_object.matrix_world.to_quaternion() @ Vector((0, 1, 0))
|
|
||||||
active_z_axis = context.active_object.matrix_world.to_quaternion() @ Vector((0, 0, 1))
|
|
||||||
|
|
||||||
x_distances = self.get_axis_distances(point, active_x_axis, context)
|
|
||||||
y_distances = self.get_axis_distances(point, active_y_axis, context)
|
|
||||||
if abs(sum(x_distances)) < abs(sum(y_distances)):
|
|
||||||
for i, obj in enumerate(selected_objs):
|
|
||||||
obj.matrix_world = Matrix.Translation(active_x_axis * -x_distances[i]) @ obj.matrix_world
|
|
||||||
else:
|
|
||||||
for i, obj in enumerate(selected_objs):
|
|
||||||
obj.matrix_world = Matrix.Translation(active_y_axis * -y_distances[i]) @ obj.matrix_world
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
def get_axis_distances(self, point: Vector, axis: Vector, context: bpy.types.Context) -> list[float]:
|
|
||||||
results = []
|
|
||||||
for obj in context.selected_objects:
|
|
||||||
if self.align_type == "CENTERLINE":
|
|
||||||
obj_point = obj.matrix_world @ (Vector(obj.bound_box[0]) + (obj.dimensions / 2))
|
|
||||||
elif self.align_type == "POSITIVE":
|
|
||||||
obj_point = obj.matrix_world @ Vector(obj.bound_box[6])
|
|
||||||
elif self.align_type == "NEGATIVE":
|
|
||||||
obj_point = obj.matrix_world @ Vector(obj.bound_box[0])
|
|
||||||
else:
|
|
||||||
assert_never(self.align_type)
|
|
||||||
results.append(mathutils.geometry.distance_point_to_plane(obj_point, point, axis))
|
|
||||||
return results
|
|
||||||
|
|
||||||
|
|
||||||
class LoadTypeThumbnails(bpy.types.Operator):
|
class LoadTypeThumbnails(bpy.types.Operator):
|
||||||
bl_idname = "bim.load_type_thumbnails"
|
bl_idname = "bim.load_type_thumbnails"
|
||||||
|
|||||||
@@ -140,31 +140,11 @@ class AlignWall(bpy.types.Operator):
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
align_type: AlignType
|
align_type: AlignType
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def poll(cls, context):
|
|
||||||
if not context.active_object:
|
|
||||||
cls.poll_message_set("No active object selected.")
|
|
||||||
return False
|
|
||||||
selected_valid_objects = tool.Model.get_selected_mesh_objects()
|
|
||||||
if len(selected_valid_objects) < 2:
|
|
||||||
cls.poll_message_set("Please select at least two mesh objects.")
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
selected_objects = tool.Model.get_selected_mesh_objects()
|
try:
|
||||||
for obj in selected_objects:
|
core.align_walls(tool.Ifc, tool.Blender, tool.Model, DumbWallAligner(), self.align_type)
|
||||||
if obj == context.active_object:
|
except core.RequireAtLeastTwoLayeredElements as e:
|
||||||
continue
|
self.report({"ERROR"}, str(e))
|
||||||
aligner = DumbWallAligner(obj, context.active_object)
|
|
||||||
if self.align_type == "CENTERLINE":
|
|
||||||
aligner.align_centerline()
|
|
||||||
elif self.align_type == "EXTERIOR":
|
|
||||||
aligner.align_first_layer()
|
|
||||||
elif self.align_type == "INTERIOR":
|
|
||||||
aligner.align_last_layer()
|
|
||||||
else:
|
|
||||||
assert_never(self.align_type)
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
@@ -613,11 +593,11 @@ class DumbWallAligner:
|
|||||||
# An alignment shifts the origin of all walls to the closest point on the
|
# An alignment shifts the origin of all walls to the closest point on the
|
||||||
# local X axis of the reference wall. In addition, the Z rotation is copied.
|
# local X axis of the reference wall. In addition, the Z rotation is copied.
|
||||||
# Z translations are ignored for alignment.
|
# Z translations are ignored for alignment.
|
||||||
def __init__(self, wall: bpy.types.Object, reference_wall: bpy.types.Object):
|
def set_reference_wall(self, reference_wall: bpy.types.Object):
|
||||||
self.wall = wall
|
|
||||||
self.reference_wall = reference_wall
|
self.reference_wall = reference_wall
|
||||||
|
|
||||||
def align_centerline(self) -> None:
|
def align_centerline(self, wall: bpy.types.Object) -> None:
|
||||||
|
self.wall = wall
|
||||||
self.align_rotation()
|
self.align_rotation()
|
||||||
|
|
||||||
l_start = Vector(self.reference_wall.bound_box[0]).lerp(Vector(self.reference_wall.bound_box[3]), 0.5)
|
l_start = Vector(self.reference_wall.bound_box[0]).lerp(Vector(self.reference_wall.bound_box[3]), 0.5)
|
||||||
@@ -635,7 +615,8 @@ class DumbWallAligner:
|
|||||||
new_origin = point - offset
|
new_origin = point - offset
|
||||||
self.wall.matrix_world.translation[0], self.wall.matrix_world.translation[1] = new_origin.xy
|
self.wall.matrix_world.translation[0], self.wall.matrix_world.translation[1] = new_origin.xy
|
||||||
|
|
||||||
def align_last_layer(self) -> None:
|
def align_last_layer(self, wall: bpy.types.Object) -> None:
|
||||||
|
self.wall = wall
|
||||||
self.align_rotation()
|
self.align_rotation()
|
||||||
|
|
||||||
if self.is_rotation_flipped():
|
if self.is_rotation_flipped():
|
||||||
@@ -658,7 +639,8 @@ class DumbWallAligner:
|
|||||||
new_origin = point - offset
|
new_origin = point - offset
|
||||||
self.wall.matrix_world.translation[0], self.wall.matrix_world.translation[1] = new_origin.xy
|
self.wall.matrix_world.translation[0], self.wall.matrix_world.translation[1] = new_origin.xy
|
||||||
|
|
||||||
def align_first_layer(self) -> None:
|
def align_first_layer(self, wall: bpy.types.Object) -> None:
|
||||||
|
self.wall = wall
|
||||||
self.align_rotation()
|
self.align_rotation()
|
||||||
|
|
||||||
if self.is_rotation_flipped():
|
if self.is_rotation_flipped():
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import bpy.utils.previews
|
|||||||
import bonsai.bim
|
import bonsai.bim
|
||||||
import bonsai.tool as tool
|
import bonsai.tool as tool
|
||||||
import bonsai.core.model as core
|
import bonsai.core.model as core
|
||||||
from bonsai.bim.module.model.wall import DumbWallJoiner
|
from bonsai.bim.module.model.wall import DumbWallJoiner, DumbWallAligner
|
||||||
from bonsai.bim.helper import prop_with_search, draw_attribute
|
from bonsai.bim.helper import prop_with_search, draw_attribute
|
||||||
from bpy.types import WorkSpaceTool, Menu
|
from bpy.types import WorkSpaceTool, Menu
|
||||||
from bonsai.bim.module.model.data import AuthoringData, ItemData
|
from bonsai.bim.module.model.data import AuthoringData, ItemData
|
||||||
@@ -1181,10 +1181,15 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
if not bpy.context.selected_objects:
|
if not bpy.context.selected_objects:
|
||||||
return
|
return
|
||||||
if self.active_material_usage == "LAYER2":
|
if self.active_material_usage == "LAYER2":
|
||||||
if bpy.ops.bim.align_wall.poll():
|
try:
|
||||||
bpy.ops.bim.align_wall(align_type="CENTERLINE")
|
core.align_walls(tool.Ifc, tool.Blender, tool.Model, DumbWallAligner(), "CENTERLINE")
|
||||||
|
except core.RequireAtLeastTwoLayeredElements as e:
|
||||||
|
self.report({"ERROR"}, str(e))
|
||||||
else:
|
else:
|
||||||
bpy.ops.bim.align_product(align_type="CENTERLINE")
|
try:
|
||||||
|
core.align_objects(tool.Blender, tool.Model, "CENTERLINE")
|
||||||
|
except core.RequireAtLeastTwoElements as e:
|
||||||
|
self.report({"ERROR"}, str(e))
|
||||||
|
|
||||||
def hotkey_S_E(self):
|
def hotkey_S_E(self):
|
||||||
if not bpy.context.selected_objects or not (active_object := bpy.context.active_object):
|
if not bpy.context.selected_objects or not (active_object := bpy.context.active_object):
|
||||||
@@ -1328,18 +1333,30 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
if not bpy.context.selected_objects:
|
if not bpy.context.selected_objects:
|
||||||
return
|
return
|
||||||
elif self.active_material_usage == "LAYER2":
|
elif self.active_material_usage == "LAYER2":
|
||||||
bpy.ops.bim.align_wall(align_type="INTERIOR")
|
try:
|
||||||
|
core.align_walls(tool.Ifc, tool.Blender, tool.Model, DumbWallAligner(), "INTERIOR")
|
||||||
|
except core.RequireAtLeastTwoLayeredElements as e:
|
||||||
|
self.report({"ERROR"}, str(e))
|
||||||
else:
|
else:
|
||||||
bpy.ops.bim.align_product(align_type="POSITIVE")
|
try:
|
||||||
|
core.align_objects(tool.Blender, tool.Model, "POSITIVE")
|
||||||
|
except core.RequireAtLeastTwoElements as e:
|
||||||
|
self.report({"ERROR"}, str(e))
|
||||||
|
|
||||||
def hotkey_S_X(self):
|
def hotkey_S_X(self):
|
||||||
if not bpy.context.selected_objects:
|
if not bpy.context.selected_objects:
|
||||||
return
|
return
|
||||||
if self.active_material_usage == "LAYER2":
|
if self.active_material_usage == "LAYER2":
|
||||||
if bpy.ops.bim.align_wall.poll():
|
try:
|
||||||
bpy.ops.bim.align_wall(align_type="EXTERIOR")
|
core.align_walls(tool.Ifc, tool.Blender, tool.Model, DumbWallAligner(), "EXTERIOR")
|
||||||
|
except core.RequireAtLeastTwoLayeredElements as e:
|
||||||
|
self.report({"ERROR"}, str(e))
|
||||||
else:
|
else:
|
||||||
bpy.ops.bim.align_product(align_type="NEGATIVE")
|
try:
|
||||||
|
core.align_objects(tool.Blender, tool.Model, "NEGATIVE")
|
||||||
|
except core.RequireAtLeastTwoElements as e:
|
||||||
|
self.report({"ERROR"}, str(e))
|
||||||
|
|
||||||
|
|
||||||
def hotkey_S_Y(self):
|
def hotkey_S_Y(self):
|
||||||
if not bpy.context.selected_objects:
|
if not bpy.context.selected_objects:
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ if TYPE_CHECKING:
|
|||||||
import bonsai.tool as tool
|
import bonsai.tool as tool
|
||||||
|
|
||||||
from mathutils import Vector
|
from mathutils import Vector
|
||||||
from bonsai.bim.module.model.wall import DumbWallJoiner
|
from bonsai.bim.module.model.wall import DumbWallJoiner, DumbWallAligner
|
||||||
|
|
||||||
|
|
||||||
def unjoin_walls(
|
def unjoin_walls(
|
||||||
@@ -82,6 +82,43 @@ def join_walls_LV(
|
|||||||
joiner.connect(another_selected_object, active_obj)
|
joiner.connect(another_selected_object, active_obj)
|
||||||
|
|
||||||
|
|
||||||
|
def align_walls(
|
||||||
|
ifc: tool.Ifc,
|
||||||
|
blender: tool.Blender,
|
||||||
|
model: tool.Model,
|
||||||
|
aligner: DumbWallAligner,
|
||||||
|
align_type: Literal["CENTERLINE", "EXTERIOR", "INTERIOR"],
|
||||||
|
):
|
||||||
|
reference_obj = blender.get_active_object(is_selected=True)
|
||||||
|
if not (e := ifc.get_entity(reference_obj) or not model.get_usage_type(e) == "LAYER2"):
|
||||||
|
reference_obj = None
|
||||||
|
objs = [
|
||||||
|
o
|
||||||
|
for o in blender.get_selected_objects()
|
||||||
|
if o != reference_obj and (e := ifc.get_entity(o)) and model.get_usage_type(e) == "LAYER2"
|
||||||
|
]
|
||||||
|
if not reference_obj or not objs:
|
||||||
|
raise RequireAtLeastTwoLayeredElements(
|
||||||
|
"At least two vertically layered elements must be selected to match alignments."
|
||||||
|
)
|
||||||
|
aligner.set_reference_wall(reference_obj)
|
||||||
|
for obj in objs:
|
||||||
|
if align_type == "CENTERLINE":
|
||||||
|
aligner.align_centerline(obj)
|
||||||
|
elif align_type == "EXTERIOR":
|
||||||
|
aligner.align_first_layer(obj)
|
||||||
|
elif align_type == "INTERIOR":
|
||||||
|
aligner.align_last_layer(obj)
|
||||||
|
|
||||||
|
|
||||||
|
def align_objects(blender: tool.Blender, model: tool.Model, align_type: Literal["CENTERLINE", "POSITIVE", "NEGATIVE"]):
|
||||||
|
reference_obj = blender.get_active_object(is_selected=True)
|
||||||
|
objs = [o for o in blender.get_selected_objects() if o != reference_obj]
|
||||||
|
if not reference_obj or not objs:
|
||||||
|
raise RequireAtLeastTwoElements("At least two objects must be selected to match alignments.")
|
||||||
|
model.align_objects(reference_obj, objs, align_type)
|
||||||
|
|
||||||
|
|
||||||
def extend_wall_to_slab(
|
def extend_wall_to_slab(
|
||||||
ifc: tool.Ifc,
|
ifc: tool.Ifc,
|
||||||
geometry: tool.Geometry,
|
geometry: tool.Geometry,
|
||||||
@@ -145,3 +182,11 @@ class RequireTwoWallsError(Exception):
|
|||||||
|
|
||||||
class RequireAtLeastTwoLayeredElements(Exception):
|
class RequireAtLeastTwoLayeredElements(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class RequireAtLeastTwoElements(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class RequireLayeredElement(Exception):
|
||||||
|
pass
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import ifcopenshell.util.unit
|
|||||||
import bonsai.core.geometry
|
import bonsai.core.geometry
|
||||||
import bonsai.core.tool
|
import bonsai.core.tool
|
||||||
import bonsai.tool as tool
|
import bonsai.tool as tool
|
||||||
|
import mathutils
|
||||||
from math import atan, cos, degrees, pi, radians
|
from math import atan, cos, degrees, pi, radians
|
||||||
from mathutils import Matrix, Vector
|
from mathutils import Matrix, Vector
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
@@ -2325,3 +2326,45 @@ class Model(bonsai.core.tool.Model):
|
|||||||
assert isinstance(group_node, bpy.types.GeometryNodeGroup)
|
assert isinstance(group_node, bpy.types.GeometryNodeGroup)
|
||||||
|
|
||||||
return [s for s in group_node.inputs if s.type != "GEOMETRY"]
|
return [s for s in group_node.inputs if s.type != "GEOMETRY"]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def align_objects(cls, reference_obj: bpy.types.Object, objs: Iterable[bpy.types.Object], align_type: Literal["CENTERLINE", "POSITIVE", "NEGATIVE"]):
|
||||||
|
if align_type == "CENTERLINE":
|
||||||
|
point = reference_obj.matrix_world @ (
|
||||||
|
Vector(reference_obj.bound_box[0]) + (reference_obj.dimensions / 2)
|
||||||
|
)
|
||||||
|
elif align_type == "POSITIVE":
|
||||||
|
point = reference_obj.matrix_world @ Vector(reference_obj.bound_box[6])
|
||||||
|
elif align_type == "NEGATIVE":
|
||||||
|
point = reference_obj.matrix_world @ Vector(reference_obj.bound_box[0])
|
||||||
|
|
||||||
|
reference_x_axis = reference_obj.matrix_world.col[0].to_3d()
|
||||||
|
reference_y_axis = reference_obj.matrix_world.col[1].to_3d()
|
||||||
|
|
||||||
|
x_distances = cls.get_axis_distances(point, reference_x_axis, objs, align_type)
|
||||||
|
y_distances = cls.get_axis_distances(point, reference_y_axis, objs, align_type)
|
||||||
|
if abs(sum(x_distances)) < abs(sum(y_distances)):
|
||||||
|
for i, obj in enumerate(objs):
|
||||||
|
obj.matrix_world = Matrix.Translation(reference_x_axis * -x_distances[i]) @ obj.matrix_world
|
||||||
|
else:
|
||||||
|
for i, obj in enumerate(objs):
|
||||||
|
obj.matrix_world = Matrix.Translation(reference_y_axis * -y_distances[i]) @ obj.matrix_world
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_axis_distances(cls, point: Vector, axis: Vector, objs: Iterable[bpy.types.Object], align_type: Literal["CENTERLINE", "POSITIVE", "NEGATIVE"]) -> list[float]:
|
||||||
|
results = []
|
||||||
|
for obj in objs:
|
||||||
|
if align_type == "CENTERLINE":
|
||||||
|
obj_point = obj.matrix_world @ (Vector(obj.bound_box[0]) + (obj.dimensions / 2))
|
||||||
|
elif align_type == "POSITIVE":
|
||||||
|
obj_point = obj.matrix_world @ Vector(obj.bound_box[6])
|
||||||
|
elif align_type == "NEGATIVE":
|
||||||
|
obj_point = obj.matrix_world @ Vector(obj.bound_box[0])
|
||||||
|
results.append(mathutils.geometry.distance_point_to_plane(obj_point, point, axis))
|
||||||
|
return results
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def offset_wall(cls, wall: bpy.types.Object, baseline: Literal["EXTERIOR", "INTERIOR", "CENTERLINE"]) -> None:
|
||||||
|
element = tool.Ifc.get_entity(wall)
|
||||||
|
if baseline == "CENTERLINE":
|
||||||
|
pass
|
||||||
|
|||||||
@@ -286,6 +286,137 @@ Scenario: Split a wall which has a flipped door
|
|||||||
And I press "bim.hotkey(hotkey='S_K')"
|
And I press "bim.hotkey(hotkey='S_K')"
|
||||||
Then the object "IfcDoor/Door" is at "8.01,0.1,0"
|
Then the object "IfcDoor/Door" is at "8.01,0.1,0"
|
||||||
|
|
||||||
|
Scenario: Align two walls - centerline
|
||||||
|
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_occurrence"
|
||||||
|
And the variable "element_type" is "[e for e in {ifc}.by_type('IfcWallType') if e.Name == 'WAL300'][0].id()"
|
||||||
|
And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}"
|
||||||
|
And the cursor is at "10,5,0"
|
||||||
|
And I press "bim.add_occurrence"
|
||||||
|
When the object "IfcWall/Wall.001" is selected
|
||||||
|
And additionally the object "IfcWall/Wall" is selected
|
||||||
|
And I press "bim.hotkey(hotkey='S_C')"
|
||||||
|
Then the object "IfcWall/Wall" bottom left corner is at "0,0,0"
|
||||||
|
And the object "IfcWall/Wall" top right corner is at "1,0.1,3"
|
||||||
|
And the object "IfcWall/Wall.001" bottom left corner is at "10,-0.1,0"
|
||||||
|
And the object "IfcWall/Wall.001" top right corner is at "11,0.2,3"
|
||||||
|
|
||||||
|
Scenario: Align two walls - interior
|
||||||
|
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_occurrence"
|
||||||
|
And the variable "element_type" is "[e for e in {ifc}.by_type('IfcWallType') if e.Name == 'WAL300'][0].id()"
|
||||||
|
And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}"
|
||||||
|
And the cursor is at "10,5,0"
|
||||||
|
And I press "bim.add_occurrence"
|
||||||
|
When the object "IfcWall/Wall.001" is selected
|
||||||
|
And additionally the object "IfcWall/Wall" is selected
|
||||||
|
And I press "bim.hotkey(hotkey='S_V')"
|
||||||
|
Then the object "IfcWall/Wall" bottom left corner is at "0,0,0"
|
||||||
|
And the object "IfcWall/Wall" top right corner is at "1,0.1,3"
|
||||||
|
And the object "IfcWall/Wall.001" bottom left corner is at "10,-0.2,0"
|
||||||
|
And the object "IfcWall/Wall.001" top right corner is at "11,0.1,3"
|
||||||
|
|
||||||
|
Scenario: Align two walls - exterior
|
||||||
|
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_occurrence"
|
||||||
|
And the variable "element_type" is "[e for e in {ifc}.by_type('IfcWallType') if e.Name == 'WAL300'][0].id()"
|
||||||
|
And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}"
|
||||||
|
And the cursor is at "10,5,0"
|
||||||
|
And I press "bim.add_occurrence"
|
||||||
|
When the object "IfcWall/Wall.001" is selected
|
||||||
|
And additionally the object "IfcWall/Wall" is selected
|
||||||
|
And I press "bim.hotkey(hotkey='S_X')"
|
||||||
|
Then the object "IfcWall/Wall" bottom left corner is at "0,0,0"
|
||||||
|
And the object "IfcWall/Wall" top right corner is at "1,0.1,3"
|
||||||
|
And the object "IfcWall/Wall.001" bottom left corner is at "10,0,0"
|
||||||
|
And the object "IfcWall/Wall.001" top right corner is at "11,0.3,3"
|
||||||
|
|
||||||
|
Scenario: Align two walls - centerline fail due to selection criteria
|
||||||
|
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_occurrence"
|
||||||
|
When the object "IfcWall/Wall" is selected
|
||||||
|
Then I press "bim.hotkey(hotkey='S_C')" and expect error "Error: At least two vertically layered elements must be selected to match alignments."
|
||||||
|
|
||||||
|
Scenario: Align two elements - centerline
|
||||||
|
Given an empty IFC project
|
||||||
|
And I load the demo construction library
|
||||||
|
And I set "scene.BIMModelProperties.ifc_class" to "IfcDoorType"
|
||||||
|
And the variable "element_type" is "[e for e in {ifc}.by_type('IfcDoorType') if e.Name == 'DT01'][0].id()"
|
||||||
|
And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}"
|
||||||
|
And I press "bim.add_occurrence"
|
||||||
|
And the cursor is at "10,5,0"
|
||||||
|
And I press "bim.add_occurrence"
|
||||||
|
And the object "IfcDoor/Door.001" is rotated by "0,0,90" deg
|
||||||
|
When the object "IfcDoor/Door.001" is selected
|
||||||
|
And additionally the object "IfcDoor/Door" is selected
|
||||||
|
And I press "bim.hotkey(hotkey='S_C')"
|
||||||
|
Then the object "IfcDoor/Door" bottom left corner is at "0,0,0"
|
||||||
|
And the object "IfcDoor/Door" top right corner is at "1.01,0.1,2.145"
|
||||||
|
And the object "IfcDoor/Door.001" bottom left corner is at "10,-0.455,0"
|
||||||
|
And the object "IfcDoor/Door.001" top right corner is at "9.9,0.555,2.145"
|
||||||
|
|
||||||
|
Scenario: Align two elements - positive
|
||||||
|
Given an empty IFC project
|
||||||
|
And I load the demo construction library
|
||||||
|
And I set "scene.BIMModelProperties.ifc_class" to "IfcDoorType"
|
||||||
|
And the variable "element_type" is "[e for e in {ifc}.by_type('IfcDoorType') if e.Name == 'DT01'][0].id()"
|
||||||
|
And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}"
|
||||||
|
And I press "bim.add_occurrence"
|
||||||
|
And the cursor is at "10,5,0"
|
||||||
|
And I press "bim.add_occurrence"
|
||||||
|
And the object "IfcDoor/Door.001" is rotated by "0,0,90" deg
|
||||||
|
When the object "IfcDoor/Door.001" is selected
|
||||||
|
And additionally the object "IfcDoor/Door" is selected
|
||||||
|
And I press "bim.hotkey(hotkey='S_V')"
|
||||||
|
Then the object "IfcDoor/Door" bottom left corner is at "0,0,0"
|
||||||
|
And the object "IfcDoor/Door" top right corner is at "1.01,0.1,2.145"
|
||||||
|
And the object "IfcDoor/Door.001" bottom left corner is at "10,-0.910,0"
|
||||||
|
And the object "IfcDoor/Door.001" top right corner is at "9.9,0.1,2.145"
|
||||||
|
|
||||||
|
Scenario: Align two elements - negative
|
||||||
|
Given an empty IFC project
|
||||||
|
And I load the demo construction library
|
||||||
|
And I set "scene.BIMModelProperties.ifc_class" to "IfcDoorType"
|
||||||
|
And the variable "element_type" is "[e for e in {ifc}.by_type('IfcDoorType') if e.Name == 'DT01'][0].id()"
|
||||||
|
And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}"
|
||||||
|
And I press "bim.add_occurrence"
|
||||||
|
And the cursor is at "10,5,0"
|
||||||
|
And I press "bim.add_occurrence"
|
||||||
|
And the object "IfcDoor/Door.001" is rotated by "0,0,90" deg
|
||||||
|
When the object "IfcDoor/Door.001" is selected
|
||||||
|
And additionally the object "IfcDoor/Door" is selected
|
||||||
|
And I press "bim.hotkey(hotkey='S_X')"
|
||||||
|
Then the object "IfcDoor/Door" bottom left corner is at "0,0,0"
|
||||||
|
And the object "IfcDoor/Door" top right corner is at "1.01,0.1,2.145"
|
||||||
|
And the object "IfcDoor/Door.001" bottom left corner is at "10,0,0"
|
||||||
|
And the object "IfcDoor/Door.001" top right corner is at "9.9,1.01,2.145"
|
||||||
|
|
||||||
|
Scenario: Align elements - fail due to selection criteria
|
||||||
|
Given an empty IFC project
|
||||||
|
And I load the demo construction library
|
||||||
|
And I set "scene.BIMModelProperties.ifc_class" to "IfcDoorType"
|
||||||
|
And the variable "element_type" is "[e for e in {ifc}.by_type('IfcDoorType') if e.Name == 'DT01'][0].id()"
|
||||||
|
And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}"
|
||||||
|
And I press "bim.add_occurrence"
|
||||||
|
When the object "IfcDoor/Door" is selected
|
||||||
|
Then I press "bim.hotkey(hotkey='S_C')" and expect error "Error: At least two objects must be selected to match alignments."
|
||||||
|
|
||||||
Scenario: Add a slab
|
Scenario: Add a slab
|
||||||
Given an empty IFC project
|
Given an empty IFC project
|
||||||
And I load the demo construction library
|
And I load the demo construction library
|
||||||
|
|||||||
@@ -1264,7 +1264,7 @@ def the_object_name_is_at_location(name, location):
|
|||||||
obj_location = the_object_name_exists(name).location
|
obj_location = the_object_name_exists(name).location
|
||||||
assert (
|
assert (
|
||||||
obj_location - Vector([float(co) for co in location.split(",")])
|
obj_location - Vector([float(co) for co in location.split(",")])
|
||||||
).length < 0.1, f"Object is at {obj_location} instead of {location}"
|
).length < 0.05, f"Object is at {obj_location} instead of {location}"
|
||||||
|
|
||||||
|
|
||||||
@then(parsers.parse('the object "{name}" has a vertex at "{location}"'))
|
@then(parsers.parse('the object "{name}" has a vertex at "{location}"'))
|
||||||
@@ -1305,7 +1305,7 @@ def the_object_name_top_right_corner_is_at_location(name, location):
|
|||||||
obj_corner = obj.matrix_world @ Vector(obj.bound_box[6])
|
obj_corner = obj.matrix_world @ Vector(obj.bound_box[6])
|
||||||
assert (
|
assert (
|
||||||
obj_corner - Vector([float(co) for co in location.split(",")])
|
obj_corner - Vector([float(co) for co in location.split(",")])
|
||||||
).length < 0.1, f"Object has top right corner {obj_corner} instead of {location}"
|
).length < 0.05, f"Object has top right corner {obj_corner} instead of {location}"
|
||||||
|
|
||||||
|
|
||||||
@then(parsers.parse('the object "{name}" bottom left corner is at "{location}"'))
|
@then(parsers.parse('the object "{name}" bottom left corner is at "{location}"'))
|
||||||
@@ -1314,7 +1314,7 @@ def the_object_name_bottom_left_corner_is_at_location(name, location):
|
|||||||
obj_corner = obj.matrix_world @ Vector(obj.bound_box[0])
|
obj_corner = obj.matrix_world @ Vector(obj.bound_box[0])
|
||||||
assert (
|
assert (
|
||||||
obj_corner - Vector([float(co) for co in location.split(",")])
|
obj_corner - Vector([float(co) for co in location.split(",")])
|
||||||
).length < 0.1, f"Object has bottom left corner {obj_corner} instead of {location}"
|
).length < 0.05, f"Object has bottom left corner {obj_corner} instead of {location}"
|
||||||
|
|
||||||
|
|
||||||
@then(parsers.parse('the object "{name}" is contained in "{container_name}"'))
|
@then(parsers.parse('the object "{name}" is contained in "{container_name}"'))
|
||||||
|
|||||||
Reference in New Issue
Block a user