WIP: started refactoring to develop the measure tool with most of the logic from polyline wall

This commit is contained in:
Bruno Perdigão
2024-08-12 22:02:43 -03:00
committed by Bruno Perdigão
parent 736b50c6fa
commit d1d9db3ed8
7 changed files with 47619 additions and 129 deletions
File diff suppressed because it is too large Load Diff
+30 -15
View File
@@ -303,6 +303,7 @@ class WallPolylineDecorator:
input_type = None input_type = None
angle_snap_mat = None angle_snap_mat = None
angle_snap_loc = None angle_snap_loc = None
use_default_container = False
@classmethod @classmethod
def install(cls, context): def install(cls, context):
@@ -336,6 +337,10 @@ class WallPolylineDecorator:
cls.axis_start = start cls.axis_start = start
cls.axis_end = end cls.axis_end = end
@classmethod
def set_use_default_container(cls, value=False):
cls.use_default_container = value
@classmethod @classmethod
def calculate_distance_and_angle(cls, context, is_input_on): def calculate_distance_and_angle(cls, context, is_input_on):
try: try:
@@ -358,7 +363,10 @@ class WallPolylineDecorator:
(float(cls.input_panel["X"]), float(cls.input_panel["Y"]), default_container_elevation) (float(cls.input_panel["X"]), float(cls.input_panel["Y"]), default_container_elevation)
) )
else: else:
snap_vector = Vector((snap_prop.x, snap_prop.y, default_container_elevation)) if cls.use_default_container:
snap_vector = Vector((snap_prop.x, snap_prop.y, default_container_elevation))
else:
snap_vector = Vector((snap_prop.x, snap_prop.y, snap_prop.z))
distance = (snap_vector - last_point).length distance = (snap_vector - last_point).length
x_axis_edge = ( x_axis_edge = (
@@ -374,6 +382,8 @@ class WallPolylineDecorator:
if cls.input_panel: if cls.input_panel:
cls.input_panel["X"] = str(round(snap_vector.x, 4)) cls.input_panel["X"] = str(round(snap_vector.x, 4))
cls.input_panel["Y"] = str(round(snap_vector.y, 4)) cls.input_panel["Y"] = str(round(snap_vector.y, 4))
if "Z" in list(cls.input_panel.keys()):
cls.input_panel["Z"] = str(round(snap_vector.z, 4))
cls.input_panel["D"] = str(round(distance, 4)) cls.input_panel["D"] = str(round(distance, 4))
cls.input_panel["A"] = str(round(angle, 4)) cls.input_panel["A"] = str(round(angle, 4))
@@ -412,7 +422,7 @@ class WallPolylineDecorator:
batch.draw(shader) batch.draw(shader)
def draw_text(self, context): def draw_text(self, context):
texts = ["X coord:", "Y coord:", "Distance:", "Angle:"] texts = {"X": "X coord:", "Y": "Y coord:", "Z": "Z coord:", "D": "Distance:", "A": "Angle:"}
self.addon_prefs = tool.Blender.get_addon_preferences() self.addon_prefs = tool.Blender.get_addon_preferences()
self.font_id = 0 self.font_id = 0
blf.size(self.font_id, 12) blf.size(self.font_id, 12)
@@ -422,14 +432,16 @@ class WallPolylineDecorator:
color_highlight = self.addon_prefs.decorator_color_special color_highlight = self.addon_prefs.decorator_color_special
offset = 20 offset = 20
new_line = 20 new_line = 20
keys = list(self.input_panel.keys()) for i, (key, value) in enumerate(self.input_panel.items()):
for i, text in enumerate(texts): if key not in list(texts.keys()):
if keys[i] == self.input_type: continue
if key == self.input_type:
blf.color(self.font_id, *color_highlight) blf.color(self.font_id, *color_highlight)
else: else:
blf.color(self.font_id, *color) blf.color(self.font_id, *color)
blf.position(self.font_id, self.mouse_pos[0] + offset, self.mouse_pos[1] + offset - (new_line * i), 0) blf.position(self.font_id, self.mouse_pos[0] + offset, self.mouse_pos[1] + offset - (new_line * i), 0)
blf.draw(self.font_id, text + self.input_panel[keys[i]]) blf.draw(self.font_id, texts[key] + value)
def __call__(self, context): def __call__(self, context):
@@ -461,15 +473,16 @@ class WallPolylineDecorator:
else: else:
self.draw_batch("POINTS", mouse_point, (1.0, 0.6, 0.0, 1.0)) self.draw_batch("POINTS", mouse_point, (1.0, 0.6, 0.0, 1.0))
# When a point is above the plane it projects the point
# to the plane and creates a line
projection_point = [] projection_point = []
if snap_prop.snap_type != "Plane" and snap_prop.z != 0: if self.use_default_container:
self.line_shader.uniform_float("lineWidth", 1.0) # When a point is above the plane it projects the point
projection_point = [Vector((snap_prop.x, snap_prop.y, default_container_elevation))] # to the plane and creates a line
self.draw_batch("POINTS", projection_point, decorator_color_unselected) if snap_prop.snap_type != "Plane" and snap_prop.z != 0:
edges = [[0, 1]] self.line_shader.uniform_float("lineWidth", 1.0)
self.draw_batch("LINES", mouse_point + projection_point, (1.0, 0.6, 0.0, 1.0), edges) projection_point = [Vector((snap_prop.x, snap_prop.y, default_container_elevation))]
self.draw_batch("POINTS", projection_point, decorator_color_unselected)
edges = [[0, 1]]
self.draw_batch("LINES", mouse_point + projection_point, (1.0, 0.6, 0.0, 1.0), edges)
# Polyline with selected points # Polyline with selected points
polyline_data = context.scene.BIMModelProperties.polyline_point polyline_data = context.scene.BIMModelProperties.polyline_point
@@ -490,9 +503,11 @@ class WallPolylineDecorator:
# Line between last polyline point and mouse # Line between last polyline point and mouse
edges = [[0, 1]] edges = [[0, 1]]
if polyline_points: if polyline_points:
if snap_prop.snap_type != "Plane" and snap_prop.z != 0 and projection_point: print("PP", polyline_points)
if snap_prop.snap_type != "Plane" and projection_point:
self.draw_batch("LINES", [polyline_points[-1]] + projection_point, decorator_color_unselected, edges) self.draw_batch("LINES", [polyline_points[-1]] + projection_point, decorator_color_unselected, edges)
else: else:
print("FOI")
self.draw_batch("LINES", [polyline_points[-1]] + mouse_point, decorator_color_unselected, edges) self.draw_batch("LINES", [polyline_points[-1]] + mouse_point, decorator_color_unselected, edges)
# Line for angle axis snap # Line for angle axis snap
+111 -110
View File
@@ -285,8 +285,6 @@ class DrawPolylineWall(bpy.types.Operator):
bl_label = "Draw Polyline Wall" bl_label = "Draw Polyline Wall"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
objs_bvhs = []
viewport_box = []
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
@@ -308,132 +306,132 @@ class DrawPolylineWall(bpy.types.Operator):
self.input_panel = {"X": "", "Y": "", "D": "", "A": ""} self.input_panel = {"X": "", "Y": "", "D": "", "A": ""}
self.snap_angle = None self.snap_angle = None
def snaping_movement(self, context, event): # def snaping_movement(self, context, event):
region = context.region # region = context.region
rv3d = context.region_data # rv3d = context.region_data
self.mouse_pos = event.mouse_region_x, event.mouse_region_y # self.mouse_pos = event.mouse_region_x, event.mouse_region_y
offset = 5 # offset = 5
mouse_offset = ( # mouse_offset = (
(-offset, offset), # (-offset, offset),
(0, offset), # (0, offset),
(offset, offset), # (offset, offset),
(-offset, 0), # (-offset, 0),
(0, 0), # (0, 0),
(offset, 0), # (offset, 0),
(-offset, -offset), # (-offset, -offset),
(0, -offset), # (0, -offset),
(offset, -offset), # (offset, -offset),
) # )
# Plane to intersect. Default Container # # Plane to intersect. Default Container
default_container_elevation = tool.Ifc.get_object(tool.Root.get_default_container()).location.z # default_container_elevation = tool.Ifc.get_object(tool.Root.get_default_container()).location.z
plane_origin = Vector((0, 0, default_container_elevation)) # plane_origin = Vector((0, 0, default_container_elevation))
plane_normal = Vector((0, 0, 1)) # plane_normal = Vector((0, 0, 1))
def cast_rays_and_get_best_object(): # def cast_rays_and_get_best_object():
best_length_squared = 1.0 # best_length_squared = 1.0
best_obj = None # best_obj = None
best_hit = None # best_hit = None
best_face_index = None # best_face_index = None
objs_to_raycast = [] # objs_to_raycast = []
for obj, bbox_2d in self.objs_2d_bbox: # for obj, bbox_2d in self.objs_2d_bbox:
if obj.type == "MESH" and bbox_2d: # if obj.type == "MESH" and bbox_2d:
if tool.Raycast.in_view_2d_bounding_box(self.mouse_pos, bbox_2d): # if tool.Raycast.in_view_2d_bounding_box(self.mouse_pos, bbox_2d):
objs_to_raycast.append(obj) # objs_to_raycast.append(obj)
for obj in objs_to_raycast: # for obj in objs_to_raycast:
hit, normal, face_index = tool.Raycast.obj_ray_cast(context, event, obj) # hit, normal, face_index = tool.Raycast.obj_ray_cast(context, event, obj)
if hit is None: # if hit is None:
# Tried original mouse position. Now it will try the offsets. # # Tried original mouse position. Now it will try the offsets.
original_mouse_pos = self.mouse_pos # original_mouse_pos = self.mouse_pos
for value in mouse_offset: # for value in mouse_offset:
self.mouse_pos = tuple(x + y for x, y in zip(original_mouse_pos, value)) # self.mouse_pos = tuple(x + y for x, y in zip(original_mouse_pos, value))
hit, normal, face_index = tool.Raycast.obj_ray_cast(context, event, obj) # hit, normal, face_index = tool.Raycast.obj_ray_cast(context, event, obj)
if hit: # if hit:
break # break
self.mouse_pos = original_mouse_pos # self.mouse_pos = original_mouse_pos
if hit is not None: # if hit is not None:
hit_world = obj.original.matrix_world @ hit # hit_world = obj.original.matrix_world @ hit
length_squared = (hit_world - ray_origin).length_squared # length_squared = (hit_world - ray_origin).length_squared
if best_obj is None or length_squared < best_length_squared: # if best_obj is None or length_squared < best_length_squared:
best_length_squared = length_squared # best_length_squared = length_squared
best_obj = obj # best_obj = obj
best_hit = hit_world # best_hit = hit_world
best_face_index = face_index # best_face_index = face_index
if best_obj is not None: # if best_obj is not None:
return best_obj, best_hit, best_face_index # return best_obj, best_hit, best_face_index
else: # else:
return None, None, None # return None, None, None
snap_threshold = 0.3 # snap_threshold = 0.3
ray_origin, ray_target, ray_direction = tool.Raycast.get_viewport_ray_data(context, event) # ray_origin, ray_target, ray_direction = tool.Raycast.get_viewport_ray_data(context, event)
obj, hit, face_index = cast_rays_and_get_best_object() # obj, hit, face_index = cast_rays_and_get_best_object()
intersection = tool.Raycast.ray_cast_to_plane(context, event, plane_origin, plane_normal) # intersection = tool.Raycast.ray_cast_to_plane(context, event, plane_origin, plane_normal)
# Locks snap into an angle axis # # Locks snap into an angle axis
if event.shift: # if event.shift:
rot_intersection, _, axis_start, axis_end = tool.Snap.snap_on_axis(intersection, self.snap_angle) # rot_intersection, _, axis_start, axis_end = tool.Snap.snap_on_axis(intersection, self.snap_angle)
else: # else:
self.snap_angle = None # self.snap_angle = None
rot_intersection, self.snap_angle, _, _ = tool.Snap.snap_on_axis(intersection) # rot_intersection, self.snap_angle, _, _ = tool.Snap.snap_on_axis(intersection)
if obj is not None: # if obj is not None:
snap_points = tool.Snap.get_snap_points_on_raycasted_obj(obj, face_index) # snap_points = tool.Snap.get_snap_points_on_raycasted_obj(obj, face_index)
snap_point = tool.Snap.select_snap_point(snap_points, hit, snap_threshold) # snap_point = tool.Snap.select_snap_point(snap_points, hit, snap_threshold)
if snap_point: # if snap_point:
# Creates a mixed snap point between the locked axis and # # Creates a mixed snap point between the locked axis and
# the object snap # # the object snap
# TODO Use ALT key to give the user the option to choose between the two results. # # TODO Use ALT key to give the user the option to choose between the two results.
# TODO Create decorator for this # # TODO Create decorator for this
try: # try:
snap_point_vector = Vector((snap_point[0].x, snap_point[0].y, snap_point[0].z)) # snap_point_vector = Vector((snap_point[0].x, snap_point[0].y, snap_point[0].z))
snap_point_axis_1 = ( # snap_point_axis_1 = (
Vector((snap_point[0].x + 1000, snap_point[0].y, default_container_elevation)), # Vector((snap_point[0].x + 1000, snap_point[0].y, default_container_elevation)),
Vector((snap_point[0].x - 1000, snap_point[0].y, default_container_elevation)), # Vector((snap_point[0].x - 1000, snap_point[0].y, default_container_elevation)),
) # )
snap_point_axis_2 = ( # snap_point_axis_2 = (
Vector((snap_point[0].x, snap_point[0].y + 1000, default_container_elevation)), # Vector((snap_point[0].x, snap_point[0].y + 1000, default_container_elevation)),
Vector((snap_point[0].x, snap_point[0].y - 1000, default_container_elevation)), # Vector((snap_point[0].x, snap_point[0].y - 1000, default_container_elevation)),
) # )
snap_angle_axis = (axis_start, axis_end) # snap_angle_axis = (axis_start, axis_end)
result_1 = tool.Cad.intersect_edges(snap_angle_axis, snap_point_axis_1) # result_1 = tool.Cad.intersect_edges(snap_angle_axis, snap_point_axis_1)
result_1 = Vector((result_1[0].x, result_1[0].y, default_container_elevation)) # result_1 = Vector((result_1[0].x, result_1[0].y, default_container_elevation))
distance_1 = (result_1 - snap_point_vector).length # distance_1 = (result_1 - snap_point_vector).length
result_2 = tool.Cad.intersect_edges(snap_angle_axis, snap_point_axis_2) # result_2 = tool.Cad.intersect_edges(snap_angle_axis, snap_point_axis_2)
result_2 = Vector((result_2[0].x, result_2[0].y, default_container_elevation)) # result_2 = Vector((result_2[0].x, result_2[0].y, default_container_elevation))
distance_2 = (result_2 - snap_point_vector).length # distance_2 = (result_2 - snap_point_vector).length
if distance_1 < distance_2: # if distance_1 < distance_2:
best_result = result_1 # best_result = result_1
else: # else:
best_result = result_2 # best_result = result_2
tool.Snap.update_snaping_point(best_result, "Axis") # tool.Snap.update_snaping_point(best_result, "Axis")
except Exception as e: # except Exception as e:
tool.Snap.update_snaping_point(snap_point[0], snap_point[1]) # tool.Snap.update_snaping_point(snap_point[0], snap_point[1])
else: # else:
tool.Snap.update_snaping_point(hit, "Face") # tool.Snap.update_snaping_point(hit, "Face")
else: # else:
snap_points = tool.Snap.get_snap_points_on_polyline() # snap_points = tool.Snap.get_snap_points_on_polyline()
snap_point = tool.Snap.select_snap_point(snap_points, intersection, snap_threshold) # snap_point = tool.Snap.select_snap_point(snap_points, intersection, snap_threshold)
if snap_point: # if snap_point:
tool.Snap.update_snaping_point(snap_point[0], snap_point[1]) # tool.Snap.update_snaping_point(snap_point[0], snap_point[1])
elif rot_intersection: # elif rot_intersection:
tool.Snap.update_snaping_point(rot_intersection, "Axis") # tool.Snap.update_snaping_point(rot_intersection, "Axis")
else: # else:
tool.Snap.update_snaping_point(intersection, "Plane") # tool.Snap.update_snaping_point(intersection, "Plane")
def validate_input(self, input_number): def validate_input(self, input_number):
grammar = """ grammar = """
@@ -540,7 +538,7 @@ class DrawPolylineWall(bpy.types.Operator):
self.objs_2d_bbox.append(tool.Raycast.get_objects_2d_bounding_boxes(context, obj)) self.objs_2d_bbox.append(tool.Raycast.get_objects_2d_bounding_boxes(context, obj))
if self.mousemove_count > 3: if self.mousemove_count > 3:
self.snaping_movement(context, event) tool.Snap.snaping_movement(context, event, self.objs_2d_bbox)
WallPolylineDecorator.set_mouse_position(event) WallPolylineDecorator.set_mouse_position(event)
self.input_panel = WallPolylineDecorator.calculate_distance_and_angle(context, self.is_input_on) self.input_panel = WallPolylineDecorator.calculate_distance_and_angle(context, self.is_input_on)
tool.Blender.update_viewport() tool.Blender.update_viewport()
@@ -660,11 +658,14 @@ class DrawPolylineWall(bpy.types.Operator):
def invoke(self, context, event): def invoke(self, context, event):
if context.space_data.type == "VIEW_3D": if context.space_data.type == "VIEW_3D":
WallPolylineDecorator.install(context) WallPolylineDecorator.install(context)
tool.Snap.set_use_default_container(True)
WallPolylineDecorator.set_use_default_container(True)
tool.Snap.set_snap_plane_method('XY')
WallPolylineDecorator.set_input_panel(self.input_panel, self.input_type) WallPolylineDecorator.set_input_panel(self.input_panel, self.input_type)
self.visible_objs = tool.Raycast.get_visible_objects(context) self.visible_objs = tool.Raycast.get_visible_objects(context)
for obj in self.visible_objs: for obj in self.visible_objs:
self.objs_2d_bbox.append(tool.Raycast.get_objects_2d_bounding_boxes(context, obj)) self.objs_2d_bbox.append(tool.Raycast.get_objects_2d_bounding_boxes(context, obj))
self.snaping_movement(context, event) tool.Snap.snaping_movement(context, event, self.objs_2d_bbox)
WallPolylineDecorator.set_mouse_position(event) WallPolylineDecorator.set_mouse_position(event)
self.input_panel = WallPolylineDecorator.calculate_distance_and_angle(context, self.is_input_on) self.input_panel = WallPolylineDecorator.calculate_distance_and_angle(context, self.is_input_on)
tool.Blender.update_viewport() tool.Blender.update_viewport()
@@ -43,6 +43,7 @@ classes = (
operator.LoadLinkedProject, operator.LoadLinkedProject,
operator.LoadProject, operator.LoadProject,
operator.LoadProjectElements, operator.LoadProjectElements,
operator.MeasureTool,
operator.NewProject, operator.NewProject,
operator.QueryLinkedElement, operator.QueryLinkedElement,
operator.RefreshClippingPlanes, operator.RefreshClippingPlanes,
@@ -52,6 +52,7 @@ from bpy.app.handlers import persistent
from ifcopenshell.geom import ShapeElementType from ifcopenshell.geom import ShapeElementType
from bonsai.bim.module.project.data import LinksData from bonsai.bim.module.project.data import LinksData
from bonsai.bim.module.project.decorator import ProjectDecorator, ClippingPlaneDecorator from bonsai.bim.module.project.decorator import ProjectDecorator, ClippingPlaneDecorator
from bonsai.bim.module.model.decorator import WallPolylineDecorator
from typing import Union from typing import Union
@@ -2286,3 +2287,92 @@ if bpy.app.version >= (4, 1, 0):
@classmethod @classmethod
def poll_drop(cls, context): def poll_drop(cls, context):
return True return True
class MeasureTool(bpy.types.Operator):
bl_idname = "bim.measure_tool"
bl_options = {"REGISTER", "UNDO"}
bl_label = "Measure Tool"
@classmethod
def poll(cls, context):
return context.space_data.type == "VIEW_3D"
def __init__(self):
self.mousemove_count = 0
self.action_count = 0
self.visible_objs = []
self.objs_2d_bbox = []
self.number_options = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ".", "+", "-", "*", "-", "/"}
self.number_input = []
self.number_output = ""
self.number_is_negative = False
self.is_input_on = False
self.input_options = ["X", "Y", "D", "A"]
self.input_type = "OFF"
self.input_value_xy = [None, None]
self.input_panel = {"X": "", "Y": "", "Z": "", "D": "", "A": ""}
self.snap_angle = None
def modal(self, context, event):
if event.type == "MOUSEMOVE":
self.mousemove_count += 1
self.input_type = "OFF"
WallPolylineDecorator.set_input_panel(self.input_panel, self.input_type)
tool.Blender.update_viewport()
else:
self.mousemove_count = 0
if self.mousemove_count == 2:
self.objs_2d_bbox = []
for obj in self.visible_objs:
self.objs_2d_bbox.append(tool.Raycast.get_objects_2d_bounding_boxes(context, obj))
if self.mousemove_count > 3:
tool.Snap.snaping_movement(context, event, self.objs_2d_bbox)
WallPolylineDecorator.set_mouse_position(event)
self.input_panel = WallPolylineDecorator.calculate_distance_and_angle(context, False)
tool.Blender.update_viewport()
return {"RUNNING_MODAL"}
if event.type in {"MIDDLEMOUSE", "WHEELUPMOUSE", "WHEELDOWNMOUSE"}:
return {"PASS_THROUGH"}
if event.value == "RELEASE" and event.type == "LEFTMOUSE":
tool.Snap.insert_polyline_point()
tool.Blender.update_viewport()
if event.value == "RELEASE" and event.type == "BACK_SPACE":
tool.Snap.remove_last_polyline_point()
tool.Blender.update_viewport()
if event.value == "RELEASE" and event.type in {"RIGHTMOUSE", "ESC"}:
WallPolylineDecorator.uninstall()
tool.Snap.clear_polyline()
tool.Blender.update_viewport()
return {"FINISHED"}
return {"RUNNING_MODAL"}
def invoke(self, context, event):
if context.space_data.type == "VIEW_3D":
WallPolylineDecorator.install(context)
tool.Snap.set_use_default_container(False)
WallPolylineDecorator.set_use_default_container(False)
tool.Snap.set_snap_plane_method('No Plane')
WallPolylineDecorator.set_input_panel(self.input_panel, self.input_type)
self.visible_objs = tool.Raycast.get_visible_objects(context)
for obj in self.visible_objs:
self.objs_2d_bbox.append(tool.Raycast.get_objects_2d_bounding_boxes(context, obj))
tool.Snap.snaping_movement(context, event, self.objs_2d_bbox)
WallPolylineDecorator.set_mouse_position(event)
self.input_panel = WallPolylineDecorator.calculate_distance_and_angle(context, self.is_input_on)
tool.Blender.update_viewport()
context.window_manager.modal_handler_add(self)
return {"RUNNING_MODAL"}
else:
self.report({"WARNING"}, "Active space must be a View3d")
return {"CANCELLED"}
@@ -37,6 +37,7 @@ class ExploreTool(bpy.types.WorkSpaceTool):
("bim.explore_hotkey", {"type": "C", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_C")]}), ("bim.explore_hotkey", {"type": "C", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_C")]}),
("bim.explore_hotkey", {"type": "F", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_F")]}), ("bim.explore_hotkey", {"type": "F", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_F")]}),
("bim.explore_hotkey", {"type": "C", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_C")]}), ("bim.explore_hotkey", {"type": "C", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_C")]}),
("bim.explore_hotkey", {"type": "M", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_M")]}),
) )
def draw_settings(context, layout, ws_tool): def draw_settings(context, layout, ws_tool):
@@ -57,6 +58,9 @@ class ExploreTool(bpy.types.WorkSpaceTool):
row = layout.row(align=True) row = layout.row(align=True)
row.label(text="", icon="EVENT_ALT") row.label(text="", icon="EVENT_ALT")
row.label(text="Disable Culling" if LinksData.enable_culling else "Enable Culling", icon="EVENT_C") row.label(text="Disable Culling" if LinksData.enable_culling else "Enable Culling", icon="EVENT_C")
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="Measure Tool", icon="EVENT_M")
class ExploreHotkey(bpy.types.Operator): class ExploreHotkey(bpy.types.Operator):
@@ -88,3 +92,6 @@ class ExploreHotkey(bpy.types.Operator):
bpy.ops.bim.disable_culling() bpy.ops.bim.disable_culling()
else: else:
bpy.ops.bim.enable_culling("INVOKE_DEFAULT") bpy.ops.bim.enable_culling("INVOKE_DEFAULT")
def hotkey_S_M(self):
bpy.ops.bim.measure_tool("INVOKE_DEFAULT")
+161 -4
View File
@@ -26,6 +26,19 @@ from mathutils import Matrix, Vector
class Snap(bonsai.core.tool.Snap): class Snap(bonsai.core.tool.Snap):
mouse_pos = None
snap_angle = None
use_default_container = False
snap_plane_method = "No Plane"
@classmethod
def set_use_default_container(cls, value=True):
cls.use_default_container = value
@classmethod
def set_snap_plane_method(cls, value=True):
cls.snap_plane_method = value
@classmethod @classmethod
def get_snap_points_on_raycasted_obj(cls, obj, face_index): def get_snap_points_on_raycasted_obj(cls, obj, face_index):
snap_points = {} snap_points = {}
@@ -91,18 +104,21 @@ class Snap(bonsai.core.tool.Snap):
snap_vertex.snap_type = snap_type snap_vertex.snap_type = snap_type
@classmethod @classmethod
def insert_polyline_point(cls, x=None, y=None): def insert_polyline_point(cls, x=None, y=None, z=None):
snap_vertex = bpy.context.scene.BIMModelProperties.snap_mouse_point[0] snap_vertex = bpy.context.scene.BIMModelProperties.snap_mouse_point[0]
polyline_point = bpy.context.scene.BIMModelProperties.polyline_point.add() polyline_point = bpy.context.scene.BIMModelProperties.polyline_point.add()
default_container_elevation = tool.Ifc.get_object(tool.Root.get_default_container()).location.z if cls.use_default_container:
elevation = tool.Ifc.get_object(tool.Root.get_default_container()).location.z
else:
elevation = snap_vertex.z
if x is not None and y is not None: if x is not None and y is not None:
polyline_point.x = x polyline_point.x = x
polyline_point.y = y polyline_point.y = y
polyline_point.z = default_container_elevation polyline_point.z = elevation
else: else:
polyline_point.x = snap_vertex.x polyline_point.x = snap_vertex.x
polyline_point.y = snap_vertex.y polyline_point.y = snap_vertex.y
polyline_point.z = default_container_elevation polyline_point.z = elevation
@classmethod @classmethod
def close_polyline(cls): def close_polyline(cls):
@@ -170,3 +186,144 @@ class Snap(bonsai.core.tool.Snap):
return snap_intersection, axis, start, end return snap_intersection, axis, start, end
return None, None, None, None return None, None, None, None
@classmethod
def snaping_movement(cls, context, event, objs_2d_bbox):
region = context.region
rv3d = context.region_data
cls.mouse_pos = event.mouse_region_x, event.mouse_region_y
offset = 5
mouse_offset = (
(-offset, offset),
(0, offset),
(offset, offset),
(-offset, 0),
(0, 0),
(offset, 0),
(-offset, -offset),
(0, -offset),
(offset, -offset),
)
# TODO Create option for different planes. RANDOM, XY, YZ, XZ. This should change the code in the bottom
if cls.snap_plane_method == "No Plane":
camera_rotation = rv3d.view_rotation
plane_origin = Vector((0, 0, 0))
plane_normal = Vector((0, 1, 1, 1)) * Vector((camera_rotation))
elif cls.snap_plane_method == "XY":
if cls.use_default_container:
elevation = tool.Ifc.get_object(tool.Root.get_default_container()).location.z
else:
elevation = 0
plane_origin = Vector((0, 0, elevation))
plane_normal = Vector((0, 0, 1))
def cast_rays_and_get_best_object():
best_length_squared = 1.0
best_obj = None
best_hit = None
best_face_index = None
objs_to_raycast = []
for obj, bbox_2d in objs_2d_bbox:
if obj.type == "MESH" and bbox_2d:
if tool.Raycast.in_view_2d_bounding_box(cls.mouse_pos, bbox_2d):
objs_to_raycast.append(obj)
for obj in objs_to_raycast:
hit, normal, face_index = tool.Raycast.obj_ray_cast(context, event, obj)
if hit is None:
# Tried original mouse position. Now it will try the offsets.
original_mouse_pos = cls.mouse_pos
for value in mouse_offset:
cls.mouse_pos = tuple(x + y for x, y in zip(original_mouse_pos, value))
hit, normal, face_index = tool.Raycast.obj_ray_cast(context, event, obj)
if hit:
break
cls.mouse_pos = original_mouse_pos
if hit is not None:
hit_world = obj.original.matrix_world @ hit
length_squared = (hit_world - ray_origin).length_squared
if best_obj is None or length_squared < best_length_squared:
best_length_squared = length_squared
best_obj = obj
best_hit = hit_world
best_face_index = face_index
if best_obj is not None:
return best_obj, best_hit, best_face_index
else:
return None, None, None
snap_threshold = 0.3
ray_origin, ray_target, ray_direction = tool.Raycast.get_viewport_ray_data(context, event)
obj, hit, face_index = cast_rays_and_get_best_object()
intersection = tool.Raycast.ray_cast_to_plane(context, event, plane_origin, plane_normal)
if cls.use_default_container:
# Locks snap into an angle axis
if event.shift:
rot_intersection, _, axis_start, axis_end = cls.snap_on_axis(intersection, cls.snap_angle)
else:
cls.snap_angle = None
rot_intersection, cls.snap_angle, _, _ = cls.snap_on_axis(intersection)
else:
rot_intersection = None
if obj is not None:
snap_points = cls.get_snap_points_on_raycasted_obj(obj, face_index)
snap_point = cls.select_snap_point(snap_points, hit, snap_threshold)
if snap_point:
# Creates a mixed snap point between the locked axis and
# the object snap
# TODO Use ALT key to give the user the option to choose between the two results.
# TODO Create decorator for this
# try:
# snap_point_vector = Vector((snap_point[0].x, snap_point[0].y, snap_point[0].z))
# snap_point_axis_1 = (
# Vector((snap_point[0].x + 1000, snap_point[0].y, default_container_elevation)),
# Vector((snap_point[0].x - 1000, snap_point[0].y, default_container_elevation)),
# )
# snap_point_axis_2 = (
# Vector((snap_point[0].x, snap_point[0].y + 1000, default_container_elevation)),
# Vector((snap_point[0].x, snap_point[0].y - 1000, default_container_elevation)),
# )
# snap_angle_axis = (axis_start, axis_end)
# result_1 = tool.Cad.intersect_edges(snap_angle_axis, snap_point_axis_1)
# result_1 = Vector((result_1[0].x, result_1[0].y, default_container_elevation))
# distance_1 = (result_1 - snap_point_vector).length
# result_2 = tool.Cad.intersect_edges(snap_angle_axis, snap_point_axis_2)
# result_2 = Vector((result_2[0].x, result_2[0].y, default_container_elevation))
# distance_2 = (result_2 - snap_point_vector).length
# if distance_1 < distance_2:
# best_result = result_1
# else:
# best_result = result_2
# cls.update_snaping_point(best_result, "Axis")
# except Exception as e:
# cls.update_snaping_point(snap_point[0], snap_point[1])
cls.update_snaping_point(snap_point[0], snap_point[1])
else:
cls.update_snaping_point(hit, "Face")
else:
snap_points = cls.get_snap_points_on_polyline()
snap_point = cls.select_snap_point(snap_points, intersection, snap_threshold)
if snap_point:
cls.update_snaping_point(snap_point[0], snap_point[1])
elif rot_intersection:
cls.update_snaping_point(rot_intersection, "Axis")
else:
cls.update_snaping_point(intersection, "Plane")