Skip to content

Commit 0052570

Browse files
committed
See #6166. First implementation of polyline extend walls.
1 parent 55e2171 commit 0052570

4 files changed

Lines changed: 142 additions & 6 deletions

File tree

src/bonsai/bonsai/bim/module/model/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
wall.DrawPolylineWall,
7676
wall.ExtendWallsToUnderside,
7777
wall.ExtendWallsToWall,
78+
wall.ExtendWallsToPolylinePoint,
7879
wall.FlipWall,
7980
wall.MergeWall,
8081
wall.OffsetWalls,

src/bonsai/bonsai/bim/module/model/wall.py

Lines changed: 138 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
import bonsai.core.model as core
3939
import bonsai.tool as tool
4040
from bonsai.bim.ifc import IfcStore
41-
from math import pi, sin, cos, degrees
41+
from math import pi, sin, cos, degrees, atan2
4242
from mathutils import Vector, Matrix
4343
from bonsai.bim.module.model.opening import FilledOpeningGenerator
4444
from bonsai.bim.module.model.decorator import PolylineDecorator, ProductDecorator
@@ -122,6 +122,139 @@ def _execute(self, context):
122122
else:
123123
self.report({"ERROR"}, "Please select at least one LAYER2 element and one active LAYER2 element")
124124

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"}
125258

126259
class AlignWall(bpy.types.Operator):
127260
bl_idname = "bim.align_wall"
@@ -1205,15 +1338,16 @@ def set_axis(self, wall, p1, p2):
12051338
else:
12061339
ifcopenshell.api.geometry.assign_representation(tool.Ifc.get(), product=wall, representation=rep)
12071340

1208-
def extend(self, wall1, target):
1341+
def extend(self, wall1, target, connection=False):
12091342
if tool.Ifc.is_moved(wall1):
12101343
bonsai.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=wall1)
12111344
element1 = tool.Ifc.get_entity(wall1)
12121345
p1, p2 = ifcopenshell.util.representation.get_reference_line(element1)
12131346
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
12141347
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"
12171351

12181352
ifcopenshell.api.run("geometry.disconnect_path", tool.Ifc.get(), element=element1, connection_type=connection)
12191353

src/bonsai/bonsai/core/model.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,14 @@ def extend_walls(
5151
joiner: DumbWallJoiner,
5252
model: tool.Model,
5353
target: Vector,
54+
connection: Optional[str]=None,
5455
) -> None:
5556
"""Extend selected walls to the target."""
5657
for obj in blender.get_selected_objects():
5758
if not (element := ifc.get_entity(obj)) or model.get_usage_type(element) != "LAYER2":
5859
continue
5960
geometry.clear_scale(obj)
60-
joiner.extend(obj, target)
61+
joiner.extend(obj, target, connection)
6162

6263

6364
def join_walls_LV(

src/bonsai/bonsai/tool/snap.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -538,7 +538,7 @@ def sort_points_by_weighted_distance(snapping_points):
538538
"object": obj,
539539
}
540540
ordered_snaps.insert(0, snap_point)
541-
cls.update_snapping_point(snap_point["point"], snap_point["type"])
541+
cls.update_snapping_point(snap_point["point"], snap_point["type"], obj)
542542
return ordered_snaps
543543
cls.update_snapping_point(point["point"], point["type"])
544544
return ordered_snaps

0 commit comments

Comments
 (0)