|
38 | 38 | import bonsai.core.model as core |
39 | 39 | import bonsai.tool as tool |
40 | 40 | from bonsai.bim.ifc import IfcStore |
41 | | -from math import pi, sin, cos, degrees |
| 41 | +from math import pi, sin, cos, degrees, atan2 |
42 | 42 | from mathutils import Vector, Matrix |
43 | 43 | from bonsai.bim.module.model.opening import FilledOpeningGenerator |
44 | 44 | from bonsai.bim.module.model.decorator import PolylineDecorator, ProductDecorator |
@@ -122,6 +122,139 @@ def _execute(self, context): |
122 | 122 | else: |
123 | 123 | self.report({"ERROR"}, "Please select at least one LAYER2 element and one active LAYER2 element") |
124 | 124 |
|
| 125 | +class ExtendWallsToPolylinePoint(bpy.types.Operator, PolylineOperator, tool.Ifc.Operator): |
| 126 | + bl_idname = "bim.extend_walls_to_polyline_point" |
| 127 | + bl_label = "Extend Walls To Polyline Point" |
| 128 | + bl_description = "Extend and trim selected walls to another wall" |
| 129 | + bl_options = {"REGISTER", "UNDO"} |
| 130 | + |
| 131 | + @classmethod |
| 132 | + def poll(cls, context): |
| 133 | + is_view_3d = context.space_data.type == "VIEW_3D" |
| 134 | + walls = True |
| 135 | + for obj in context.selected_objects: |
| 136 | + if not tool.Ifc.get_entity(obj).is_a("IfcWall"): |
| 137 | + walls = False |
| 138 | + |
| 139 | + return bool(context.selected_objects) and is_view_3d and walls |
| 140 | + |
| 141 | + def __init__(self): |
| 142 | + super().__init__() |
| 143 | + self.connection = "ATEND" |
| 144 | + |
| 145 | + def set_origin(self, context, event, connection="ATSTART"): |
| 146 | + obj = context.active_object |
| 147 | + element = tool.Ifc.get_entity(obj) |
| 148 | + layers = tool.Model.get_material_layer_parameters(element) |
| 149 | + axis = tool.Model.get_wall_axis(obj, layers) |
| 150 | + start = Vector((axis["reference"][0][0], axis["reference"][0][1], obj.location.z)) |
| 151 | + end = Vector((axis["reference"][1][0], axis["reference"][1][1], obj.location.z)) |
| 152 | + direcion = end - start |
| 153 | + value = end if connection=="ATSTART" else start |
| 154 | + self.input_ui.set_value("X", value[0]) |
| 155 | + self.input_ui.set_value("Y", value[1]) |
| 156 | + self.input_ui.set_value("Z", value[2]) |
| 157 | + result = tool.Polyline.insert_polyline_point(self.input_ui, self.tool_state) |
| 158 | + PolylineDecorator.update(event, self.tool_state, self.input_ui, self.snapping_points[0]) |
| 159 | + tool.Blender.update_viewport() |
| 160 | + # Point related to the mouse |
| 161 | + snap_prop = context.scene.BIMPolylineProperties.snap_mouse_point[0] |
| 162 | + mouse_point = Vector((snap_prop.x, snap_prop.y, snap_prop.z)) |
| 163 | + |
| 164 | + angle = atan2(direcion.y, direcion.x) |
| 165 | + |
| 166 | + self.tool_state.lock_axis = True |
| 167 | + self.tool_state.snap_angle = degrees(angle) |
| 168 | + |
| 169 | + def modal(self, context, event): |
| 170 | + return IfcStore.execute_ifc_operator(self, context, event, method="MODAL") |
| 171 | + |
| 172 | + def _modal(self, context, event): |
| 173 | + PolylineDecorator.update(event, self.tool_state, self.input_ui, self.snapping_points[0]) |
| 174 | + tool.Blender.update_viewport() |
| 175 | + self.handle_lock_axis(context, event) # Must come before "PASS_THROUGH" |
| 176 | + self.handle_mouse_move(context, event) |
| 177 | + |
| 178 | + if event.type in {"MIDDLEMOUSE", "WHEELUPMOUSE", "WHEELDOWNMOUSE"}: |
| 179 | + self.handle_mouse_move(context, event) |
| 180 | + return {"PASS_THROUGH"} |
| 181 | + |
| 182 | + custom_instructions = { |
| 183 | + "Cycle Input": {"icons": True, "keys": ["EVENT_TAB"]}, |
| 184 | + "Distance Input": {"icons": True, "keys": ["EVENT_D"]}, |
| 185 | + "Flip starting point": {"icons": True, "keys": ["EVENT_F"]}, |
| 186 | + "Confirm": {"icons": True, "keys": ["MOUSE_LMB"]}, |
| 187 | + "Cancel": {"icons": True, "keys": ["MOUSE_RMB", "EVENT_ESC"]}, |
| 188 | + } |
| 189 | + custom_info = [] |
| 190 | + self.handle_instructions(context, custom_instructions, custom_info, overwrite=True) |
| 191 | + self.handle_mouse_move(context, event, should_round=True) |
| 192 | + self.choose_axis(event) |
| 193 | + self.handle_snap_selection(context, event) |
| 194 | + |
| 195 | + if event.value == "RELEASE" and event.type == "F": |
| 196 | + tool.Polyline.clear_polyline() |
| 197 | + self.connection = "ATSTART" if self.connection=="ATEND" else "ATEND" |
| 198 | + self.set_origin(context, event, self.connection) |
| 199 | + |
| 200 | + if ( |
| 201 | + event.value == "RELEASE" |
| 202 | + and event.type in {"RET", "NUMPAD_ENTER", "RIGHTMOUSE", "LEFTMOUSE"} |
| 203 | + ): |
| 204 | + if self.tool_state.is_input_on: |
| 205 | + is_valid = self.recalculate_inputs(context) |
| 206 | + if is_valid: |
| 207 | + result = tool.Polyline.insert_polyline_point(self.input_ui, self.tool_state) |
| 208 | + if result: |
| 209 | + self.report({"WARNING"}, result) |
| 210 | + else: |
| 211 | + result = tool.Polyline.insert_polyline_point(self.input_ui, self.tool_state) |
| 212 | + if result: |
| 213 | + self.report({"WARNING"}, result) |
| 214 | + |
| 215 | + snap_prop = context.scene.BIMPolylineProperties.snap_mouse_point[0] |
| 216 | + snap_obj = bpy.data.objects.get(snap_prop.snap_object) |
| 217 | + if snap_obj and tool.Ifc.get_entity(snap_obj).is_a("IfcWall"): |
| 218 | + tool.Blender.set_active_object(snap_obj) |
| 219 | + ExtendWallsToWall._execute(self, context) |
| 220 | + else: |
| 221 | + point = context.scene.BIMPolylineProperties.insertion_polyline[0].polyline_points[1] |
| 222 | + core.extend_walls( |
| 223 | + tool.Ifc, |
| 224 | + tool.Blender, |
| 225 | + tool.Geometry, |
| 226 | + DumbWallJoiner(), |
| 227 | + tool.Model, |
| 228 | + Vector((point.x, point.y, point.z)), |
| 229 | + self.connection, |
| 230 | + ) |
| 231 | + |
| 232 | + tool.Polyline.clear_polyline() |
| 233 | + context.workspace.status_text_set(text=None) |
| 234 | + PolylineDecorator.uninstall() |
| 235 | + tool.Blender.update_viewport() |
| 236 | + return {"FINISHED"} |
| 237 | + |
| 238 | + |
| 239 | + self.handle_keyboard_input(context, event) |
| 240 | + |
| 241 | + cancel = self.handle_cancelation(context, event) |
| 242 | + if cancel is not None: |
| 243 | + return cancel |
| 244 | + |
| 245 | + return {"RUNNING_MODAL"} |
| 246 | + |
| 247 | + def invoke(self, context, event): |
| 248 | + super().invoke(context, event) |
| 249 | + self.set_origin(context, event, self.connection) |
| 250 | + self.tool_state.use_default_container = True |
| 251 | + self.tool_state.plane_method = "XY" |
| 252 | + # Update snaps after changing plane_method |
| 253 | + detected_snaps = tool.Snap.detect_snapping_points(context, event, self.objs_2d_bbox, self.tool_state) |
| 254 | + self.snapping_points = tool.Snap.select_snapping_points(context, event, self.tool_state, detected_snaps) |
| 255 | + tool.Polyline.calculate_distance_and_angle(context, self.input_ui, self.tool_state) |
| 256 | + tool.Blender.update_viewport() |
| 257 | + return {"RUNNING_MODAL"} |
125 | 258 |
|
126 | 259 | class AlignWall(bpy.types.Operator): |
127 | 260 | bl_idname = "bim.align_wall" |
@@ -1205,15 +1338,16 @@ def set_axis(self, wall, p1, p2): |
1205 | 1338 | else: |
1206 | 1339 | ifcopenshell.api.geometry.assign_representation(tool.Ifc.get(), product=wall, representation=rep) |
1207 | 1340 |
|
1208 | | - def extend(self, wall1, target): |
| 1341 | + def extend(self, wall1, target, connection=False): |
1209 | 1342 | if tool.Ifc.is_moved(wall1): |
1210 | 1343 | bonsai.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=wall1) |
1211 | 1344 | element1 = tool.Ifc.get_entity(wall1) |
1212 | 1345 | p1, p2 = ifcopenshell.util.representation.get_reference_line(element1) |
1213 | 1346 | unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) |
1214 | 1347 | target = (wall1.matrix_world.inverted() @ target).to_2d() / unit_scale |
1215 | | - intersect, connection = mathutils.geometry.intersect_point_line(target, p1, p2) |
1216 | | - connection = "ATEND" if connection > 0.5 else "ATSTART" |
| 1348 | + intersect, intersection_point = mathutils.geometry.intersect_point_line(target, p1, p2) |
| 1349 | + if not connection: |
| 1350 | + connection = "ATEND" if intersection_point > 0.5 else "ATSTART" |
1217 | 1351 |
|
1218 | 1352 | ifcopenshell.api.run("geometry.disconnect_path", tool.Ifc.get(), element=element1, connection_type=connection) |
1219 | 1353 |
|
|
0 commit comments