mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-21 11:53:44 +00:00
People kept on getting confused with the align tool thinking it changed the baseline to reference line offset whereas it actually just aligned the object bodies. This tool now does exactly that. Also start refactoring the "DumbWall" classes into the tools so we can test them properly.
This commit is contained in:
@@ -77,6 +77,7 @@ classes = (
|
|||||||
wall.ExtendWallsToWall,
|
wall.ExtendWallsToWall,
|
||||||
wall.FlipWall,
|
wall.FlipWall,
|
||||||
wall.MergeWall,
|
wall.MergeWall,
|
||||||
|
wall.OffsetWalls,
|
||||||
wall.RecalculateWall,
|
wall.RecalculateWall,
|
||||||
wall.SplitWall,
|
wall.SplitWall,
|
||||||
wall.UnjoinWalls,
|
wall.UnjoinWalls,
|
||||||
|
|||||||
@@ -599,6 +599,7 @@ class PolylineOperator:
|
|||||||
"Increment Angle": {"icons": True, "keys": ["EVENT_SHIFT", "MOUSE_MMB_SCROLL"]},
|
"Increment Angle": {"icons": True, "keys": ["EVENT_SHIFT", "MOUSE_MMB_SCROLL"]},
|
||||||
"Modify Snap Point": {"icons": True, "keys": ["EVENT_M"]},
|
"Modify Snap Point": {"icons": True, "keys": ["EVENT_M"]},
|
||||||
"Close Polyline": {"icons": True, "keys": ["EVENT_C"]},
|
"Close Polyline": {"icons": True, "keys": ["EVENT_C"]},
|
||||||
|
"Offset": {"icons": True, "keys": ["EVENT_O"]},
|
||||||
"Remove Point": {"icons": True, "keys": ["EVENT_BACKSPACE"]},
|
"Remove Point": {"icons": True, "keys": ["EVENT_BACKSPACE"]},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ import bonsai.core.root
|
|||||||
from bonsai.bim.ifc import IfcStore
|
from bonsai.bim.ifc import IfcStore
|
||||||
from math import pi, degrees, atan2
|
from math import pi, degrees, atan2
|
||||||
from mathutils import Vector, Matrix
|
from mathutils import Vector, Matrix
|
||||||
from bonsai.bim.module.model.wall import DumbWallRecalculator
|
|
||||||
from bonsai.bim.module.model.decorator import ProfileDecorator, PolylineDecorator, ProductDecorator
|
from bonsai.bim.module.model.decorator import ProfileDecorator, PolylineDecorator, ProductDecorator
|
||||||
from bonsai.bim.module.model.polyline import PolylineOperator
|
from bonsai.bim.module.model.polyline import PolylineOperator
|
||||||
from typing import Union, Any, Optional
|
from typing import Union, Any, Optional
|
||||||
@@ -969,7 +968,7 @@ class Rotate90(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
obj.matrix_world @= rotate_matrix
|
obj.matrix_world @= rotate_matrix
|
||||||
bpy.context.view_layer.update()
|
bpy.context.view_layer.update()
|
||||||
DumbProfileRecalculator().recalculate(profile_objs)
|
DumbProfileRecalculator().recalculate(profile_objs)
|
||||||
DumbWallRecalculator().recalculate(layer2_objs)
|
tool.Model.recalculate_walls(layer2_objs)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -221,13 +221,13 @@ class BIMModelProperties(PropertyGroup):
|
|||||||
items=[("EXTERIOR", "Exterior", ""), ("CENTER", "Center", ""), ("INTERIOR", "Interior", "")],
|
items=[("EXTERIOR", "Exterior", ""), ("CENTER", "Center", ""), ("INTERIOR", "Interior", "")],
|
||||||
name="Vertical Layer Offset Type",
|
name="Vertical Layer Offset Type",
|
||||||
default="EXTERIOR",
|
default="EXTERIOR",
|
||||||
description="It's a convention that affects the offset to reference line",
|
description="Offset convention to reference line",
|
||||||
)
|
)
|
||||||
offset_type_horizontal: bpy.props.EnumProperty(
|
offset_type_horizontal: bpy.props.EnumProperty(
|
||||||
items=[("TOP", "Top", ""), ("CENTER", "Center", ""), ("BOTTOM", "Bottom", "")],
|
items=[("TOP", "Top", ""), ("CENTER", "Center", ""), ("BOTTOM", "Bottom", "")],
|
||||||
name="Horizontal Layer Offset Type",
|
name="Horizontal Layer Offset Type",
|
||||||
default="TOP",
|
default="TOP",
|
||||||
description="It's a convention that affects the offset to reference line",
|
description="Offset convention to reference line",
|
||||||
)
|
)
|
||||||
offset: bpy.props.FloatProperty(name="Offset", default=0.0, description="Material usage offset from reference line")
|
offset: bpy.props.FloatProperty(name="Offset", default=0.0, description="Material usage offset from reference line")
|
||||||
show_wall_axis: bpy.props.BoolProperty(
|
show_wall_axis: bpy.props.BoolProperty(
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ from math import cos, pi
|
|||||||
from mathutils import Vector, Matrix
|
from mathutils import Vector, Matrix
|
||||||
from bonsai.bim.module.model.decorator import ProfileDecorator, PolylineDecorator, ProductDecorator
|
from bonsai.bim.module.model.decorator import ProfileDecorator, PolylineDecorator, ProductDecorator
|
||||||
from bonsai.bim.module.model.polyline import PolylineOperator
|
from bonsai.bim.module.model.polyline import PolylineOperator
|
||||||
from bonsai.bim.module.model.wall import DumbWallRecalculator
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
@@ -1035,5 +1034,5 @@ class RecalculateSlab(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
if rel.is_a() == "IfcRelConnectsElements" and rel.RelatedElement.is_a("IfcWall"):
|
if rel.is_a() == "IfcRelConnectsElements" and rel.RelatedElement.is_a("IfcWall"):
|
||||||
walls.append(tool.Ifc.get_object(rel.RelatedElement))
|
walls.append(tool.Ifc.get_object(rel.RelatedElement))
|
||||||
|
|
||||||
DumbWallRecalculator().recalculate(walls)
|
tool.Model.recalculate_walls(walls)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|||||||
@@ -117,8 +117,8 @@ class ExtendWallsToWall(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
ifcopenshell.api.geometry.connect_wall(
|
ifcopenshell.api.geometry.connect_wall(
|
||||||
tool.Ifc.get(), wall1=element, wall2=target_element, is_atpath=True
|
tool.Ifc.get(), wall1=element, wall2=target_element, is_atpath=True
|
||||||
)
|
)
|
||||||
joiner.recreate_wall(element, obj)
|
tool.Model.recreate_wall(element, obj)
|
||||||
joiner.recreate_wall(target_element, target_obj)
|
tool.Model.recreate_wall(target_element, target_obj)
|
||||||
else:
|
else:
|
||||||
self.report({"ERROR"}, "Please select at least one LAYER2 element and one active LAYER2 element")
|
self.report({"ERROR"}, "Please select at least one LAYER2 element and one active LAYER2 element")
|
||||||
|
|
||||||
@@ -129,10 +129,10 @@ class AlignWall(bpy.types.Operator):
|
|||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
bl_description = """ Align the selected walls to the active wall:
|
bl_description = """ Align the selected walls to the active wall:
|
||||||
'Ext.': align to the EXTERIOR face
|
'Ext.': align to the EXTERIOR face
|
||||||
'C/L': align to wall CENTERLINE
|
'C/L': align to wall CENTER
|
||||||
'Int.': align to the INTERIOR face"""
|
'Int.': align to the INTERIOR face"""
|
||||||
|
|
||||||
AlignType = Literal["CENTERLINE", "EXTERIOR", "INTERIOR"]
|
AlignType = Literal["CENTER", "EXTERIOR", "INTERIOR"]
|
||||||
align_type: bpy.props.EnumProperty( # type: ignore [reportRedeclaration]
|
align_type: bpy.props.EnumProperty( # type: ignore [reportRedeclaration]
|
||||||
items=((i, i, "") for i in get_args(AlignType))
|
items=((i, i, "") for i in get_args(AlignType))
|
||||||
)
|
)
|
||||||
@@ -233,7 +233,7 @@ class RecalculateWall(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
|
|
||||||
def _execute(self, context):
|
def _execute(self, context):
|
||||||
objects = tool.Model.get_selected_mesh_ifc_objects()
|
objects = tool.Model.get_selected_mesh_ifc_objects()
|
||||||
DumbWallRecalculator().recalculate(objects)
|
tool.Model.recalculate_walls(objects)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
@@ -280,7 +280,7 @@ class ChangeExtrusionDepth(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
layer2_objs.append(obj)
|
layer2_objs.append(obj)
|
||||||
|
|
||||||
if layer2_objs:
|
if layer2_objs:
|
||||||
DumbWallRecalculator().recalculate(layer2_objs)
|
tool.Model.recalculate_walls(layer2_objs)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
@@ -393,7 +393,7 @@ class ChangeExtrusionXAngle(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
obj.rotation_euler.z = current_z_rot
|
obj.rotation_euler.z = current_z_rot
|
||||||
|
|
||||||
if layer2_objs:
|
if layer2_objs:
|
||||||
DumbWallRecalculator().recalculate(layer2_objs)
|
tool.Model.recalculate_walls(layer2_objs)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
@@ -416,7 +416,24 @@ class ChangeLayerLength(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
selected_objs = tool.Model.get_selected_mesh_ifc_objects()
|
selected_objs = tool.Model.get_selected_mesh_ifc_objects()
|
||||||
for obj in selected_objs:
|
for obj in selected_objs:
|
||||||
joiner.set_length(obj, self.length)
|
joiner.set_length(obj, self.length)
|
||||||
return {"FINISHED"}
|
|
||||||
|
|
||||||
|
class OffsetWalls(bpy.types.Operator, tool.Ifc.Operator):
|
||||||
|
bl_idname = "bim.offset_walls"
|
||||||
|
bl_label = "Offset Walls"
|
||||||
|
bl_description = "Offset selected objects from their reference line."
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
if not tool.Model.has_selected_mesh_ifc_objects():
|
||||||
|
cls.poll_message_set("No mesh IFC objects selected.")
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _execute(self, context):
|
||||||
|
props = tool.Model.get_model_props()
|
||||||
|
core.offset_walls(tool.Ifc, tool.Blender, tool.Model, props.offset_type_vertical)
|
||||||
|
|
||||||
|
|
||||||
class AddWallsFromSlab(bpy.types.Operator, tool.Ifc.Operator):
|
class AddWallsFromSlab(bpy.types.Operator, tool.Ifc.Operator):
|
||||||
@@ -487,12 +504,8 @@ class DrawPolylineWall(bpy.types.Operator, PolylineOperator, tool.Ifc.Operator):
|
|||||||
material_set_usage = model.by_id(material.id())
|
material_set_usage = model.by_id(material.id())
|
||||||
# if material.is_a("IfcMaterialLayerSetUsage"):
|
# if material.is_a("IfcMaterialLayerSetUsage"):
|
||||||
attributes = {"OffsetFromReferenceLine": offset, "DirectionSense": direction_sense}
|
attributes = {"OffsetFromReferenceLine": offset, "DirectionSense": direction_sense}
|
||||||
ifcopenshell.api.run(
|
ifcopenshell.api.run("material.edit_layer_usage", model, usage=material_set_usage, attributes=attributes)
|
||||||
"material.edit_layer_usage",
|
tool.Model.recalculate_walls([wall["obj"]])
|
||||||
model,
|
|
||||||
**{"usage": material_set_usage, "attributes": attributes},
|
|
||||||
)
|
|
||||||
DumbWallRecalculator().recalculate([wall["obj"]])
|
|
||||||
|
|
||||||
if walls:
|
if walls:
|
||||||
if is_polyline_closed:
|
if is_polyline_closed:
|
||||||
@@ -682,30 +695,6 @@ class DumbWallAligner:
|
|||||||
return round(degrees(angle) % 360) == 180
|
return round(degrees(angle) % 360) == 180
|
||||||
|
|
||||||
|
|
||||||
class DumbWallRecalculator:
|
|
||||||
def recalculate(self, walls: list[bpy.types.Object]) -> None:
|
|
||||||
queue: set[tuple[ifcopenshell.entity_instance, bpy.types.Object]] = set()
|
|
||||||
for wall in walls:
|
|
||||||
element = tool.Ifc.get_entity(wall)
|
|
||||||
if tool.Ifc.is_moved(wall):
|
|
||||||
bonsai.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=wall)
|
|
||||||
queue.add((element, wall))
|
|
||||||
for rel in getattr(element, "ConnectedTo", []):
|
|
||||||
obj = tool.Ifc.get_object(rel.RelatedElement)
|
|
||||||
if tool.Ifc.is_moved(obj):
|
|
||||||
bonsai.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
|
|
||||||
queue.add((rel.RelatedElement, obj))
|
|
||||||
for rel in getattr(element, "ConnectedFrom", []):
|
|
||||||
obj = tool.Ifc.get_object(rel.RelatingElement)
|
|
||||||
if tool.Ifc.is_moved(obj):
|
|
||||||
bonsai.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
|
|
||||||
queue.add((rel.RelatingElement, obj))
|
|
||||||
joiner = DumbWallJoiner()
|
|
||||||
for element, wall in queue:
|
|
||||||
if tool.Model.get_usage_type(element) == "LAYER2" and wall:
|
|
||||||
joiner.recreate_wall(element, wall)
|
|
||||||
|
|
||||||
|
|
||||||
class DumbWallGenerator:
|
class DumbWallGenerator:
|
||||||
def __init__(self, relating_type):
|
def __init__(self, relating_type):
|
||||||
self.relating_type = relating_type
|
self.relating_type = relating_type
|
||||||
@@ -937,7 +926,7 @@ class DumbWallPlaner:
|
|||||||
else:
|
else:
|
||||||
for rel in inverse.AssociatedTo:
|
for rel in inverse.AssociatedTo:
|
||||||
walls.extend([tool.Ifc.get_object(e) for e in rel.RelatedObjects])
|
walls.extend([tool.Ifc.get_object(e) for e in rel.RelatedObjects])
|
||||||
DumbWallRecalculator().recalculate([w for w in set(walls) if w])
|
tool.Model.recalculate_walls([w for w in set(walls) if w])
|
||||||
|
|
||||||
def regenerate_from_type(self, usecase_path, ifc_file, settings):
|
def regenerate_from_type(self, usecase_path, ifc_file, settings):
|
||||||
relating_type = settings["relating_type"]
|
relating_type = settings["relating_type"]
|
||||||
@@ -968,7 +957,7 @@ class DumbWallPlaner:
|
|||||||
if layer_set_direction:
|
if layer_set_direction:
|
||||||
material.LayerSetDirection = layer_set_direction
|
material.LayerSetDirection = layer_set_direction
|
||||||
if material.LayerSetDirection == "AXIS2":
|
if material.LayerSetDirection == "AXIS2":
|
||||||
DumbWallRecalculator().recalculate([obj])
|
tool.Model.recalculate_walls([obj])
|
||||||
|
|
||||||
|
|
||||||
class DumbWallJoiner:
|
class DumbWallJoiner:
|
||||||
@@ -988,7 +977,7 @@ class DumbWallJoiner:
|
|||||||
axis1 = tool.Model.get_wall_axis(wall1)
|
axis1 = tool.Model.get_wall_axis(wall1)
|
||||||
axis = copy.deepcopy(axis1["reference"])
|
axis = copy.deepcopy(axis1["reference"])
|
||||||
body = copy.deepcopy(axis1["reference"])
|
body = copy.deepcopy(axis1["reference"])
|
||||||
self.recreate_wall(element1, wall1, axis, body)
|
tool.Model.recreate_wall(element1, wall1)
|
||||||
|
|
||||||
def split(self, wall1: bpy.types.Object, target: Vector) -> None:
|
def split(self, wall1: bpy.types.Object, target: Vector) -> None:
|
||||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||||
@@ -1089,8 +1078,8 @@ class DumbWallJoiner:
|
|||||||
self.set_axis(element1, p1, p3)
|
self.set_axis(element1, p1, p3)
|
||||||
self.set_axis(element2, p3, p2)
|
self.set_axis(element2, p3, p2)
|
||||||
|
|
||||||
self.recreate_wall(element1, wall1)
|
tool.Model.recreate_wall(element1, wall1)
|
||||||
self.recreate_wall(element2, wall2)
|
tool.Model.recreate_wall(element2, wall2)
|
||||||
|
|
||||||
def flip(self, wall1: bpy.types.Object) -> None:
|
def flip(self, wall1: bpy.types.Object) -> None:
|
||||||
if tool.Ifc.is_moved(wall1):
|
if tool.Ifc.is_moved(wall1):
|
||||||
@@ -1117,7 +1106,7 @@ class DumbWallJoiner:
|
|||||||
ifcopenshell.api.geometry.edit_object_placement(
|
ifcopenshell.api.geometry.edit_object_placement(
|
||||||
tool.Ifc.get(), product=element1, matrix=matrix, is_si=False, should_transform_children=False
|
tool.Ifc.get(), product=element1, matrix=matrix, is_si=False, should_transform_children=False
|
||||||
)
|
)
|
||||||
self.recreate_wall(element1, wall1)
|
tool.Model.recreate_wall(element1, wall1)
|
||||||
|
|
||||||
def merge(self, wall1: bpy.types.Object, wall2: bpy.types.Object) -> None:
|
def merge(self, wall1: bpy.types.Object, wall2: bpy.types.Object) -> None:
|
||||||
if tool.Ifc.is_moved(wall1):
|
if tool.Ifc.is_moved(wall1):
|
||||||
@@ -1170,7 +1159,7 @@ class DumbWallJoiner:
|
|||||||
related_connection=rel.RelatedConnectionType,
|
related_connection=rel.RelatedConnectionType,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.recreate_wall(element1, wall1)
|
tool.Model.recreate_wall(element1, wall1)
|
||||||
|
|
||||||
tool.Geometry.delete_ifc_object(wall2)
|
tool.Geometry.delete_ifc_object(wall2)
|
||||||
|
|
||||||
@@ -1203,7 +1192,7 @@ class DumbWallJoiner:
|
|||||||
description="TOP",
|
description="TOP",
|
||||||
)
|
)
|
||||||
|
|
||||||
self.recreate_wall(element1, wall1)
|
tool.Model.recreate_wall(element1, wall1)
|
||||||
|
|
||||||
def set_axis(self, wall, p1, p2):
|
def set_axis(self, wall, p1, p2):
|
||||||
axis = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Plan", "Axis", "GRAPH_VIEW")
|
axis = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Plan", "Axis", "GRAPH_VIEW")
|
||||||
@@ -1232,7 +1221,7 @@ class DumbWallJoiner:
|
|||||||
self.set_axis(element1, p1, intersect)
|
self.set_axis(element1, p1, intersect)
|
||||||
else:
|
else:
|
||||||
self.set_axis(element1, intersect, p2)
|
self.set_axis(element1, intersect, p2)
|
||||||
self.recreate_wall(element1, wall1)
|
tool.Model.recreate_wall(element1, wall1)
|
||||||
|
|
||||||
def set_length(self, wall1: bpy.types.Object, si_length: float) -> None:
|
def set_length(self, wall1: bpy.types.Object, si_length: float) -> None:
|
||||||
element1 = tool.Ifc.get_entity(wall1)
|
element1 = tool.Ifc.get_entity(wall1)
|
||||||
@@ -1246,7 +1235,7 @@ class DumbWallJoiner:
|
|||||||
p1, p2 = ifcopenshell.util.representation.get_reference_line(element1)
|
p1, p2 = ifcopenshell.util.representation.get_reference_line(element1)
|
||||||
p2[0] = p1[0] + si_length / unit_scale
|
p2[0] = p1[0] + si_length / unit_scale
|
||||||
self.set_axis(element1, p1, p2)
|
self.set_axis(element1, p1, p2)
|
||||||
self.recreate_wall(element1, wall1)
|
tool.Model.recreate_wall(element1, wall1)
|
||||||
|
|
||||||
def join_T(self, wall1: bpy.types.Object, wall2: bpy.types.Object) -> None:
|
def join_T(self, wall1: bpy.types.Object, wall2: bpy.types.Object) -> None:
|
||||||
element1 = tool.Ifc.get_entity(wall1)
|
element1 = tool.Ifc.get_entity(wall1)
|
||||||
@@ -1269,7 +1258,7 @@ class DumbWallJoiner:
|
|||||||
description="BUTT",
|
description="BUTT",
|
||||||
)
|
)
|
||||||
|
|
||||||
self.recreate_wall(element1, wall1, axis1["reference"], axis1["reference"])
|
tool.Model.recreate_wall(element1, wall1, axis1["reference"], axis1["reference"])
|
||||||
|
|
||||||
def connect(self, obj1: bpy.types.Object, obj2: bpy.types.Object) -> None:
|
def connect(self, obj1: bpy.types.Object, obj2: bpy.types.Object) -> None:
|
||||||
wall1 = tool.Ifc.get_entity(obj1)
|
wall1 = tool.Ifc.get_entity(obj1)
|
||||||
@@ -1279,27 +1268,8 @@ class DumbWallJoiner:
|
|||||||
if tool.Ifc.is_moved(obj2):
|
if tool.Ifc.is_moved(obj2):
|
||||||
bonsai.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj2)
|
bonsai.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj2)
|
||||||
ifcopenshell.api.geometry.connect_wall(tool.Ifc.get(), wall1=wall1, wall2=wall2)
|
ifcopenshell.api.geometry.connect_wall(tool.Ifc.get(), wall1=wall1, wall2=wall2)
|
||||||
self.recreate_wall(wall1, obj1)
|
tool.Model.recreate_wall(wall1, obj1)
|
||||||
self.recreate_wall(wall2, obj2)
|
tool.Model.recreate_wall(wall2, obj2)
|
||||||
|
|
||||||
def recreate_wall(self, element: ifcopenshell.entity_instance, obj: bpy.types.Object, axis=None, body=None) -> None:
|
|
||||||
rep = ifcopenshell.api.geometry.regenerate_wall_representation(tool.Ifc.get(), element)
|
|
||||||
bonsai.core.geometry.switch_representation(
|
|
||||||
tool.Ifc,
|
|
||||||
tool.Geometry,
|
|
||||||
obj=obj,
|
|
||||||
representation=rep,
|
|
||||||
should_reload=True,
|
|
||||||
is_global=True,
|
|
||||||
should_sync_changes_first=False,
|
|
||||||
)
|
|
||||||
tool.Geometry.record_object_materials(obj)
|
|
||||||
|
|
||||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
|
||||||
matrix = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
|
|
||||||
matrix[:, 3] *= unit_scale
|
|
||||||
obj.matrix_world = tool.Loader.apply_blender_offset_to_matrix_world(obj, matrix)
|
|
||||||
tool.Geometry.record_object_position(obj)
|
|
||||||
|
|
||||||
def create_matrix(self, p, x, y, z):
|
def create_matrix(self, p, x, y, z):
|
||||||
return Matrix([x, y, z, p]).to_4x4().transposed()
|
return Matrix([x, y, z, p]).to_4x4().transposed()
|
||||||
|
|||||||
@@ -773,6 +773,12 @@ class EditObjectUI:
|
|||||||
op = row.operator("bim.change_extrusion_x_angle", icon="FILE_REFRESH", text="")
|
op = row.operator("bim.change_extrusion_x_angle", icon="FILE_REFRESH", text="")
|
||||||
op.x_angle = cls.props.x_angle
|
op.x_angle = cls.props.x_angle
|
||||||
|
|
||||||
|
row = cls.layout.row(align=True) if ui_context != "TOOL_HEADER" else row
|
||||||
|
row.prop(
|
||||||
|
data=cls.props, property="offset_type_vertical", text="Offset" if ui_context != "TOOL_HEADER" else ""
|
||||||
|
)
|
||||||
|
row.operator("bim.offset_walls", icon="FILE_REFRESH", text="")
|
||||||
|
|
||||||
elif AuthoringData.data["active_material_usage"] == "LAYER3":
|
elif AuthoringData.data["active_material_usage"] == "LAYER3":
|
||||||
row.prop(data=cls.props, property="x_angle", text="Angle" if ui_context != "TOOL_HEADER" else "A")
|
row.prop(data=cls.props, property="x_angle", text="Angle" if ui_context != "TOOL_HEADER" else "A")
|
||||||
op = row.operator("bim.change_extrusion_x_angle", icon="FILE_REFRESH", text="")
|
op = row.operator("bim.change_extrusion_x_angle", icon="FILE_REFRESH", text="")
|
||||||
@@ -1182,12 +1188,12 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
return
|
return
|
||||||
if self.active_material_usage == "LAYER2":
|
if self.active_material_usage == "LAYER2":
|
||||||
try:
|
try:
|
||||||
core.align_walls(tool.Ifc, tool.Blender, tool.Model, DumbWallAligner(), "CENTERLINE")
|
core.align_walls(tool.Ifc, tool.Blender, tool.Model, DumbWallAligner(), "CENTER")
|
||||||
except core.RequireAtLeastTwoLayeredElements as e:
|
except core.RequireAtLeastTwoLayeredElements as e:
|
||||||
self.report({"ERROR"}, str(e))
|
self.report({"ERROR"}, str(e))
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
core.align_objects(tool.Blender, tool.Model, "CENTERLINE")
|
core.align_objects(tool.Blender, tool.Model, "CENTER")
|
||||||
except core.RequireAtLeastTwoElements as e:
|
except core.RequireAtLeastTwoElements as e:
|
||||||
self.report({"ERROR"}, str(e))
|
self.report({"ERROR"}, str(e))
|
||||||
|
|
||||||
@@ -1357,7 +1363,6 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
except core.RequireAtLeastTwoElements as e:
|
except core.RequireAtLeastTwoElements as e:
|
||||||
self.report({"ERROR"}, str(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:
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ if TYPE_CHECKING:
|
|||||||
from mathutils import Vector
|
from mathutils import Vector
|
||||||
from bonsai.bim.module.model.wall import DumbWallJoiner, DumbWallAligner
|
from bonsai.bim.module.model.wall import DumbWallJoiner, DumbWallAligner
|
||||||
|
|
||||||
|
AlignType = Literal["CENTER", "EXTERIOR", "INTERIOR"]
|
||||||
|
OffsetType = Literal["CENTER", "EXTERIOR", "INTERIOR"]
|
||||||
|
|
||||||
|
|
||||||
def unjoin_walls(
|
def unjoin_walls(
|
||||||
ifc: tool.Ifc, blender: tool.Blender, geometry: tool.Geometry, joiner: DumbWallJoiner, model: tool.Model
|
ifc: tool.Ifc, blender: tool.Blender, geometry: tool.Geometry, joiner: DumbWallJoiner, model: tool.Model
|
||||||
@@ -82,12 +85,19 @@ def join_walls_LV(
|
|||||||
joiner.connect(another_selected_object, active_obj)
|
joiner.connect(another_selected_object, active_obj)
|
||||||
|
|
||||||
|
|
||||||
|
def offset_walls(ifc: tool.Ifc, blender: tool.Blender, model: tool.Model, offset_type: OffsetType):
|
||||||
|
objs = [
|
||||||
|
obj
|
||||||
|
for obj in blender.get_selected_objects()
|
||||||
|
if (element := ifc.get_entity(obj)) and model.get_usage_type(element) == "LAYER2"
|
||||||
|
]
|
||||||
|
for obj in objs:
|
||||||
|
model.offset_wall(obj, offset_type)
|
||||||
|
model.recalculate_walls(objs)
|
||||||
|
|
||||||
|
|
||||||
def align_walls(
|
def align_walls(
|
||||||
ifc: tool.Ifc,
|
ifc: tool.Ifc, blender: tool.Blender, model: tool.Model, aligner: DumbWallAligner, align_type: AlignType
|
||||||
blender: tool.Blender,
|
|
||||||
model: tool.Model,
|
|
||||||
aligner: DumbWallAligner,
|
|
||||||
align_type: Literal["CENTERLINE", "EXTERIOR", "INTERIOR"],
|
|
||||||
):
|
):
|
||||||
reference_obj = blender.get_active_object(is_selected=True)
|
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"):
|
if not (e := ifc.get_entity(reference_obj) or not model.get_usage_type(e) == "LAYER2"):
|
||||||
@@ -103,7 +113,7 @@ def align_walls(
|
|||||||
)
|
)
|
||||||
aligner.set_reference_wall(reference_obj)
|
aligner.set_reference_wall(reference_obj)
|
||||||
for obj in objs:
|
for obj in objs:
|
||||||
if align_type == "CENTERLINE":
|
if align_type == "CENTER":
|
||||||
aligner.align_centerline(obj)
|
aligner.align_centerline(obj)
|
||||||
elif align_type == "EXTERIOR":
|
elif align_type == "EXTERIOR":
|
||||||
aligner.align_first_layer(obj)
|
aligner.align_first_layer(obj)
|
||||||
@@ -111,7 +121,7 @@ def align_walls(
|
|||||||
aligner.align_last_layer(obj)
|
aligner.align_last_layer(obj)
|
||||||
|
|
||||||
|
|
||||||
def align_objects(blender: tool.Blender, model: tool.Model, align_type: Literal["CENTERLINE", "POSITIVE", "NEGATIVE"]):
|
def align_objects(blender: tool.Blender, model: tool.Model, align_type: Literal["CENTER", "POSITIVE", "NEGATIVE"]):
|
||||||
reference_obj = blender.get_active_object(is_selected=True)
|
reference_obj = blender.get_active_object(is_selected=True)
|
||||||
objs = [o for o in blender.get_selected_objects() if o != reference_obj]
|
objs = [o for o in blender.get_selected_objects() if o != reference_obj]
|
||||||
if not reference_obj or not objs:
|
if not reference_obj or not objs:
|
||||||
|
|||||||
@@ -2328,11 +2328,14 @@ class Model(bonsai.core.tool.Model):
|
|||||||
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
|
@classmethod
|
||||||
def align_objects(cls, reference_obj: bpy.types.Object, objs: Iterable[bpy.types.Object], align_type: Literal["CENTERLINE", "POSITIVE", "NEGATIVE"]):
|
def align_objects(
|
||||||
if align_type == "CENTERLINE":
|
cls,
|
||||||
point = reference_obj.matrix_world @ (
|
reference_obj: bpy.types.Object,
|
||||||
Vector(reference_obj.bound_box[0]) + (reference_obj.dimensions / 2)
|
objs: Iterable[bpy.types.Object],
|
||||||
)
|
align_type: Literal["CENTER", "POSITIVE", "NEGATIVE"],
|
||||||
|
):
|
||||||
|
if align_type == "CENTER":
|
||||||
|
point = reference_obj.matrix_world @ (Vector(reference_obj.bound_box[0]) + (reference_obj.dimensions / 2))
|
||||||
elif align_type == "POSITIVE":
|
elif align_type == "POSITIVE":
|
||||||
point = reference_obj.matrix_world @ Vector(reference_obj.bound_box[6])
|
point = reference_obj.matrix_world @ Vector(reference_obj.bound_box[6])
|
||||||
elif align_type == "NEGATIVE":
|
elif align_type == "NEGATIVE":
|
||||||
@@ -2351,10 +2354,16 @@ class Model(bonsai.core.tool.Model):
|
|||||||
obj.matrix_world = Matrix.Translation(reference_y_axis * -y_distances[i]) @ obj.matrix_world
|
obj.matrix_world = Matrix.Translation(reference_y_axis * -y_distances[i]) @ obj.matrix_world
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_axis_distances(cls, point: Vector, axis: Vector, objs: Iterable[bpy.types.Object], align_type: Literal["CENTERLINE", "POSITIVE", "NEGATIVE"]) -> list[float]:
|
def get_axis_distances(
|
||||||
|
cls,
|
||||||
|
point: Vector,
|
||||||
|
axis: Vector,
|
||||||
|
objs: Iterable[bpy.types.Object],
|
||||||
|
align_type: Literal["CENTER", "POSITIVE", "NEGATIVE"],
|
||||||
|
) -> list[float]:
|
||||||
results = []
|
results = []
|
||||||
for obj in objs:
|
for obj in objs:
|
||||||
if align_type == "CENTERLINE":
|
if align_type == "CENTER":
|
||||||
obj_point = obj.matrix_world @ (Vector(obj.bound_box[0]) + (obj.dimensions / 2))
|
obj_point = obj.matrix_world @ (Vector(obj.bound_box[0]) + (obj.dimensions / 2))
|
||||||
elif align_type == "POSITIVE":
|
elif align_type == "POSITIVE":
|
||||||
obj_point = obj.matrix_world @ Vector(obj.bound_box[6])
|
obj_point = obj.matrix_world @ Vector(obj.bound_box[6])
|
||||||
@@ -2364,7 +2373,66 @@ class Model(bonsai.core.tool.Model):
|
|||||||
return results
|
return results
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def offset_wall(cls, wall: bpy.types.Object, baseline: Literal["EXTERIOR", "INTERIOR", "CENTERLINE"]) -> None:
|
def offset_wall(cls, wall: bpy.types.Object, baseline: Literal["EXTERIOR", "INTERIOR", "CENTER"]) -> None:
|
||||||
element = tool.Ifc.get_entity(wall)
|
element = tool.Ifc.get_entity(wall)
|
||||||
if baseline == "CENTERLINE":
|
usage = ifcopenshell.util.element.get_material(element)
|
||||||
pass
|
if not usage.is_a("IfcMaterialLayerSetUsage"):
|
||||||
|
return
|
||||||
|
layer_set = usage.ForLayerSet
|
||||||
|
if baseline == "CENTER":
|
||||||
|
if usage.DirectionSense == "POSITIVE":
|
||||||
|
usage.OffsetFromReferenceLine = -layer_set.TotalThickness / 2
|
||||||
|
else:
|
||||||
|
usage.OffsetFromReferenceLine = layer_set.TotalThickness / 2
|
||||||
|
elif baseline == "INTERIOR":
|
||||||
|
if usage.DirectionSense == "POSITIVE":
|
||||||
|
usage.OffsetFromReferenceLine = -layer_set.TotalThickness
|
||||||
|
else:
|
||||||
|
usage.OffsetFromReferenceLine = 0.0
|
||||||
|
elif baseline == "EXTERIOR":
|
||||||
|
if usage.DirectionSense == "POSITIVE":
|
||||||
|
usage.OffsetFromReferenceLine = 0.0
|
||||||
|
else:
|
||||||
|
usage.OffsetFromReferenceLine = layer_set.TotalThickness
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def recreate_wall(cls, element: ifcopenshell.entity_instance, obj: bpy.types.Object) -> None:
|
||||||
|
rep = ifcopenshell.api.geometry.regenerate_wall_representation(tool.Ifc.get(), element)
|
||||||
|
bonsai.core.geometry.switch_representation(
|
||||||
|
tool.Ifc,
|
||||||
|
tool.Geometry,
|
||||||
|
obj=obj,
|
||||||
|
representation=rep,
|
||||||
|
should_reload=True,
|
||||||
|
is_global=True,
|
||||||
|
should_sync_changes_first=False,
|
||||||
|
)
|
||||||
|
tool.Geometry.record_object_materials(obj)
|
||||||
|
|
||||||
|
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||||
|
matrix = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
|
||||||
|
matrix[:, 3] *= unit_scale
|
||||||
|
obj.matrix_world = tool.Loader.apply_blender_offset_to_matrix_world(obj, matrix)
|
||||||
|
tool.Geometry.record_object_position(obj)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def recalculate_walls(cls, walls: list[bpy.types.Object]) -> None:
|
||||||
|
queue: set[tuple[ifcopenshell.entity_instance, bpy.types.Object]] = set()
|
||||||
|
for wall in walls:
|
||||||
|
element = tool.Ifc.get_entity(wall)
|
||||||
|
if tool.Ifc.is_moved(wall):
|
||||||
|
bonsai.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=wall)
|
||||||
|
queue.add((element, wall))
|
||||||
|
for rel in getattr(element, "ConnectedTo", []):
|
||||||
|
obj = tool.Ifc.get_object(rel.RelatedElement)
|
||||||
|
if tool.Ifc.is_moved(obj):
|
||||||
|
bonsai.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
|
||||||
|
queue.add((rel.RelatedElement, obj))
|
||||||
|
for rel in getattr(element, "ConnectedFrom", []):
|
||||||
|
obj = tool.Ifc.get_object(rel.RelatingElement)
|
||||||
|
if tool.Ifc.is_moved(obj):
|
||||||
|
bonsai.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
|
||||||
|
queue.add((rel.RelatingElement, obj))
|
||||||
|
for element, wall in queue:
|
||||||
|
if tool.Model.get_usage_type(element) == "LAYER2" and wall:
|
||||||
|
cls.recreate_wall(element, wall)
|
||||||
|
|||||||
@@ -286,7 +286,28 @@ 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
|
Scenario: Offset 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_occurrence"
|
||||||
|
When the object "IfcWall/Wall" is selected
|
||||||
|
And I set "scene.BIMModelProperties.offset_type_vertical" to "EXTERIOR"
|
||||||
|
And I press "bim.offset_walls"
|
||||||
|
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"
|
||||||
|
When I set "scene.BIMModelProperties.offset_type_vertical" to "INTERIOR"
|
||||||
|
And I press "bim.offset_walls"
|
||||||
|
Then the object "IfcWall/Wall" bottom left corner is at "0,-0.1,0"
|
||||||
|
And the object "IfcWall/Wall" top right corner is at "1,0,3"
|
||||||
|
When I set "scene.BIMModelProperties.offset_type_vertical" to "CENTER"
|
||||||
|
And I press "bim.offset_walls"
|
||||||
|
Then the object "IfcWall/Wall" bottom left corner is at "0,-0.05,0"
|
||||||
|
And the object "IfcWall/Wall" top right corner is at "1,0.05,3"
|
||||||
|
|
||||||
|
Scenario: Align walls
|
||||||
Given an empty IFC project
|
Given an empty IFC project
|
||||||
And I load the demo construction library
|
And I load the demo construction library
|
||||||
And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType"
|
And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType"
|
||||||
@@ -304,46 +325,18 @@ Scenario: Align two walls - centerline
|
|||||||
And the object "IfcWall/Wall" top right corner is at "1,0.1,3"
|
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" bottom left corner is at "10,-0.1,0"
|
||||||
And the object "IfcWall/Wall.001" top right corner is at "11,0.2,3"
|
And the object "IfcWall/Wall.001" top right corner is at "11,0.2,3"
|
||||||
|
When I press "bim.hotkey(hotkey='S_V')"
|
||||||
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"
|
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" 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" bottom left corner is at "10,-0.2,0"
|
||||||
And the object "IfcWall/Wall.001" top right corner is at "11,0.1,3"
|
And the object "IfcWall/Wall.001" top right corner is at "11,0.1,3"
|
||||||
|
When I press "bim.hotkey(hotkey='S_X')"
|
||||||
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"
|
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" 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" bottom left corner is at "10,0,0"
|
||||||
And the object "IfcWall/Wall.001" top right corner is at "11,0.3,3"
|
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
|
Scenario: Align walls - centerline fail due to selection criteria
|
||||||
Given an empty IFC project
|
Given an empty IFC project
|
||||||
And I load the demo construction library
|
And I load the demo construction library
|
||||||
And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType"
|
And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType"
|
||||||
@@ -353,7 +346,7 @@ Scenario: Align two walls - centerline fail due to selection criteria
|
|||||||
When the object "IfcWall/Wall" is selected
|
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."
|
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
|
Scenario: Align elements
|
||||||
Given an empty IFC project
|
Given an empty IFC project
|
||||||
And I load the demo construction library
|
And I load the demo construction library
|
||||||
And I set "scene.BIMModelProperties.ifc_class" to "IfcDoorType"
|
And I set "scene.BIMModelProperties.ifc_class" to "IfcDoorType"
|
||||||
@@ -370,38 +363,12 @@ Scenario: Align two elements - centerline
|
|||||||
And the object "IfcDoor/Door" top right corner is at "1.01,0.1,2.145"
|
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" 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"
|
And the object "IfcDoor/Door.001" top right corner is at "9.9,0.555,2.145"
|
||||||
|
When I press "bim.hotkey(hotkey='S_V')"
|
||||||
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"
|
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" 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" 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"
|
And the object "IfcDoor/Door.001" top right corner is at "9.9,0.1,2.145"
|
||||||
|
When I press "bim.hotkey(hotkey='S_X')"
|
||||||
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"
|
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" 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" bottom left corner is at "10,0,0"
|
||||||
|
|||||||
@@ -678,3 +678,44 @@ class TestApplyIfcMaterialChanges(NewFile):
|
|||||||
ifcopenshell.api.material.unassign_material(ifc_file, products=[element])
|
ifcopenshell.api.material.unassign_material(ifc_file, products=[element])
|
||||||
tool.Material.ensure_material_unassigned([element])
|
tool.Material.ensure_material_unassigned([element])
|
||||||
assert self.get_mesh(obj).materials[:] == [bpy.data.materials["Red"]]
|
assert self.get_mesh(obj).materials[:] == [bpy.data.materials["Red"]]
|
||||||
|
|
||||||
|
|
||||||
|
class TestOffsetWall(NewFile):
|
||||||
|
def test_run(self):
|
||||||
|
ifc = ifcopenshell.file()
|
||||||
|
tool.Ifc.set(ifc)
|
||||||
|
|
||||||
|
wall_type = ifcopenshell.api.root.create_entity(ifc, ifc_class="IfcWallType", name="WAL01")
|
||||||
|
material_set = ifcopenshell.api.material.add_material_set(ifc, set_type="IfcMaterialLayerSet")
|
||||||
|
material = ifcopenshell.api.material.add_material(ifc, name="PB01", category="gypsum")
|
||||||
|
layer = ifcopenshell.api.material.add_layer(ifc, layer_set=material_set, material=material)
|
||||||
|
ifcopenshell.api.material.edit_layer(ifc, layer=layer, attributes={"LayerThickness": 100})
|
||||||
|
ifcopenshell.api.material.assign_material(ifc, products=[wall_type], material=material_set)
|
||||||
|
|
||||||
|
wall = ifcopenshell.api.root.create_entity(ifc, ifc_class="IfcWall")
|
||||||
|
ifcopenshell.api.type.assign_type(ifc, related_objects=[wall], relating_type=wall_type)
|
||||||
|
rel = ifcopenshell.api.material.assign_material(ifc, products=[wall], type="IfcMaterialLayerSetUsage")
|
||||||
|
usage = rel.RelatingMaterial
|
||||||
|
obj = bpy.data.objects.new("Wall", None)
|
||||||
|
tool.Ifc.link(wall, obj)
|
||||||
|
|
||||||
|
usage.DirectionSense = "POSITIVE"
|
||||||
|
subject.offset_wall(obj, "CENTER")
|
||||||
|
assert usage.OffsetFromReferenceLine == -50
|
||||||
|
usage.DirectionSense = "NEGATIVE"
|
||||||
|
subject.offset_wall(obj, "CENTER")
|
||||||
|
assert usage.OffsetFromReferenceLine == 50
|
||||||
|
|
||||||
|
usage.DirectionSense = "POSITIVE"
|
||||||
|
subject.offset_wall(obj, "INTERIOR")
|
||||||
|
assert usage.OffsetFromReferenceLine == -100
|
||||||
|
usage.DirectionSense = "NEGATIVE"
|
||||||
|
subject.offset_wall(obj, "INTERIOR")
|
||||||
|
assert usage.OffsetFromReferenceLine == 0
|
||||||
|
|
||||||
|
usage.DirectionSense = "POSITIVE"
|
||||||
|
subject.offset_wall(obj, "EXTERIOR")
|
||||||
|
assert usage.OffsetFromReferenceLine == 0
|
||||||
|
usage.DirectionSense = "NEGATIVE"
|
||||||
|
subject.offset_wall(obj, "EXTERIOR")
|
||||||
|
assert usage.OffsetFromReferenceLine == 100
|
||||||
|
|||||||
Reference in New Issue
Block a user