From d3f2711fe49795370f24aad6c2ec5c463df01019 Mon Sep 17 00:00:00 2001 From: falken10 Date: Fri, 16 May 2025 11:06:10 +0200 Subject: [PATCH] Moved everything to explore tool --- .../bonsai/bim/module/geometry/__init__.py | 13 - .../bonsai/bim/module/geometry/operator.py | 76 ----- src/bonsai/bonsai/bim/module/geometry/ui.py | 203 +------------ .../bonsai/bim/module/project/__init__.py | 5 + .../bonsai/bim/module/project/operator.py | 276 ++++++++++++++++++ src/bonsai/bonsai/bim/module/project/prop.py | 7 +- .../bonsai/bim/module/project/workspace.py | 2 + 7 files changed, 290 insertions(+), 292 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/geometry/__init__.py b/src/bonsai/bonsai/bim/module/geometry/__init__.py index c19ba8089d..690f02f870 100644 --- a/src/bonsai/bonsai/bim/module/geometry/__init__.py +++ b/src/bonsai/bonsai/bim/module/geometry/__init__.py @@ -77,7 +77,6 @@ classes = ( operator.UpdateItemAttributes, operator.UpdateParametricRepresentation, operator.UpdateRepresentation, - operator.BIM_OT_local_coordinates_gizmo, operator.CreateInstance, prop.RepresentationItem, prop.RepresentationItemObject, @@ -135,15 +134,6 @@ def register(): bpy.types.Object.BIMGeometryProperties = bpy.props.PointerProperty(type=prop.BIMObjectGeometryProperties) bpy.types.Scene.BIMGeometryProperties = bpy.props.PointerProperty(type=prop.BIMGeometryProperties) - bpy.types.Scene.show_colored_dimensions = bpy.props.BoolProperty( - name="Show Colored Dimensions", - description="Show XYZ dimensions in color. Local coordinates for single selection. Global coordinates for multiple selection. Toggles gizmo", - default=False, - update=ui.BIM_PT_derived_coordinates.update_show_colored_dimensions, - ) - bpy.types.Scene.bonsai_prev_orientation_type = bpy.props.StringProperty(name="Prev Orientation Type") - bpy.types.Scene.bonsai_prev_gizmo_translate = bpy.props.BoolProperty(name="Prev Gizmo Translate") - bpy.types.VIEW3D_MT_object.append(ui.object_menu) bpy.types.OUTLINER_MT_object.append(ui.outliner_menu) bpy.types.VIEW3D_MT_object_context_menu.append(ui.object_menu) @@ -225,6 +215,3 @@ def unregister(): km.keymap_items.remove(kmi) addon_keymaps.clear() - del bpy.types.Scene.show_colored_dimensions - del bpy.types.Scene._bonsai_prev_orientation_type - del bpy.types.Scene._bonsai_prev_gizmo_translate diff --git a/src/bonsai/bonsai/bim/module/geometry/operator.py b/src/bonsai/bonsai/bim/module/geometry/operator.py index 2e6d1b4966..2b414cc043 100644 --- a/src/bonsai/bonsai/bim/module/geometry/operator.py +++ b/src/bonsai/bonsai/bim/module/geometry/operator.py @@ -3470,79 +3470,3 @@ class CreateInstance(bpy.types.Operator, tool.Ifc.Operator): return {"FINISHED"} - -previous_selection = set() - - -def selection_monitor_handler(scene, depsgraph): - global previous_selection - - # Get the current selection - current_selection = {obj.name for obj in scene.objects if obj.select_get()} - - # If selection changed, print and update - if current_selection != previous_selection: - if len(bpy.context.selected_objects) == 1: - scene.transform_orientation_slots[1].type = "LOCAL" - else: - scene.transform_orientation_slots[1].type = "GLOBAL" - area = next((a for a in bpy.context.screen.areas if a.type == "VIEW_3D"), None) - space = next((s for s in area.spaces if s.type == "VIEW_3D"), None) - space.show_gizmo_object_translate = True - previous_selection = current_selection - - -def register_handler(): - unregister_handler() - bpy.app.handlers.depsgraph_update_post.append(selection_monitor_handler) - - -def unregister_handler(): - handlers = bpy.app.handlers.depsgraph_update_post - handlers[:] = [h for h in handlers if h.__name__ != "selection_monitor_handler"] - - -class BIM_OT_local_coordinates_gizmo(bpy.types.Operator): - bl_idname = "bim.toggle_local_gizmo" - bl_label = "Show Local Gizmo" - - def execute(self, context): - scene = bpy.context.scene - area_3d = next((area for area in bpy.context.screen.areas if area.type == "VIEW_3D"), None) - space_3d = next((space for space in area_3d.spaces if space.type == "VIEW_3D"), None) if area_3d else None - - try: - if context.scene.show_colored_dimensions: - register_handler() - bpy.app.handlers.depsgraph_update_post.append( - selection_monitor_handler - ) # Save initial state only if not already saved - if not scene.get("bonsai_prev_orientation_type", None): - scene["bonsai_prev_orientation_type"] = scene.transform_orientation_slots[1].type - if not scene.get("bonsai_prev_gizmo_translate", None): - scene["bonsai_prev_gizmo_translate"] = space_3d.show_gizmo_object_translate if space_3d else False - - # Set orientation and gizmo based on selection count - if space_3d: - if len(context.selected_objects) == 1: - scene.transform_orientation_slots[1].type = "LOCAL" - else: - scene.transform_orientation_slots[1].type = "GLOBAL" - space_3d.show_gizmo_object_translate = True - - else: - unregister_handler() - # Restore previous state - if space_3d: - prev_orientation = scene.get("bonsai_prev_orientation_type", None) - if prev_orientation: - scene.transform_orientation_slots[1].type = prev_orientation - scene["bonsai_prev_orientation_type"] = "" - prev_gizmo = scene.get("bonsai_prev_gizmo_translate", None) - if prev_gizmo is not None: - space_3d.show_gizmo_object_translate = prev_gizmo - scene["bonsai_prev_gizmo_translate"] = None - - except Exception: - self.report({"WARNING"}, "Could not set transform orientation.") - return {"FINISHED"} diff --git a/src/bonsai/bonsai/bim/module/geometry/ui.py b/src/bonsai/bonsai/bim/module/geometry/ui.py index 400d5f8382..7b2a9818df 100644 --- a/src/bonsai/bonsai/bim/module/geometry/ui.py +++ b/src/bonsai/bonsai/bim/module/geometry/ui.py @@ -19,10 +19,6 @@ import bpy import bonsai.bim import bonsai.tool as tool -import gpu -import mathutils -import blf -from gpu_extras.batch import batch_for_shader from bpy.types import Panel, Menu, UIList from bonsai.bim.helper import prop_with_search from bonsai.bim.module.geometry.data import ( @@ -35,196 +31,6 @@ from bonsai.bim.module.geometry.data import ( from bonsai.bim.module.layer.data import LayersData -_draw_handler_box = None -_draw_handler_text = None - - -def get_combined_bounding_box_corners(objects): - import mathutils - - if not objects: - return None, None, None - - if len(objects) == 1: - obj = bpy.context.active_object - corners = [obj.matrix_world @ mathutils.Vector(corner) for corner in obj.bound_box] - - else: - all_corners = [] - for obj in objects: - if hasattr(obj, "bound_box"): - all_corners.extend([obj.matrix_world @ mathutils.Vector(corner) for corner in obj.bound_box]) - - min_corner = mathutils.Vector( - (min(v.x for v in all_corners), min(v.y for v in all_corners), min(v.z for v in all_corners)) - ) - max_corner = mathutils.Vector( - (max(v.x for v in all_corners), max(v.y for v in all_corners), max(v.z for v in all_corners)) - ) - corners = [ - mathutils.Vector((min_corner.x, min_corner.y, min_corner.z)), - mathutils.Vector((min_corner.x, min_corner.y, max_corner.z)), - mathutils.Vector((min_corner.x, max_corner.y, max_corner.z)), - mathutils.Vector((min_corner.x, max_corner.y, min_corner.z)), - mathutils.Vector((max_corner.x, min_corner.y, min_corner.z)), - mathutils.Vector((max_corner.x, min_corner.y, max_corner.z)), - mathutils.Vector((max_corner.x, max_corner.y, max_corner.z)), - mathutils.Vector((max_corner.x, max_corner.y, min_corner.z)), - ] - - edges = [ - (0, 1, "Z"), - (1, 2, "Y"), - (2, 3, "Z"), - (3, 0, "Y"), - (4, 5, "Z"), - (5, 6, "Y"), - (6, 7, "Z"), - (7, 4, "Y"), - (0, 4, "X"), - (1, 5, "X"), - (2, 6, "X"), - (3, 7, "X"), - ] - axis_colors = { - "X": (0.956, 0.282, 0.322, 1), - "Y": (0.565, 0.812, 0.125, 1), - "Z": (0.196, 0.529, 0.929, 1), - } - return corners, edges, axis_colors - - -def find_closest_trihedron(corners, edges, region, rv3d): - from bpy_extras.view3d_utils import location_3d_to_region_2d - - # Project all corners to 2D and find the one with the lowest Y value - min_y = float("inf") - best_origin = None - for idx, corner in enumerate(corners): - screen_co = location_3d_to_region_2d(region, rv3d, corner) - if screen_co is not None and screen_co.y < min_y: - min_y = screen_co.y - best_origin = idx - - trihedron = [ - {"X": (0, 4), "Y": (0, 3), "Z": (0, 1)}, - {"X": (1, 5), "Y": (1, 2), "Z": (1, 0)}, - {"X": (2, 6), "Y": (2, 1), "Z": (2, 3)}, - {"X": (3, 7), "Y": (3, 0), "Z": (3, 2)}, - {"X": (4, 0), "Y": (4, 7), "Z": (4, 5)}, - {"X": (5, 1), "Y": (5, 6), "Z": (5, 4)}, - {"X": (6, 2), "Y": (6, 5), "Z": (6, 7)}, - {"X": (7, 3), "Y": (7, 4), "Z": (7, 6)}, - ] - - return trihedron[best_origin] - - -def draw_bounding_box_wire_cube(): - selected_objects = [obj for obj in bpy.context.selected_objects if hasattr(obj, "bound_box")] - if not selected_objects: - return - corners, edges, axis_colors = get_combined_bounding_box_corners(selected_objects) - region = bpy.context.region - rv3d = bpy.context.region_data - - shader = gpu.shader.from_builtin("UNIFORM_COLOR") - gpu.state.line_width_set(2.0) - - # Draw the bounding box edges - for i1, i2, axis in edges: - color = (0.5, 0.5, 0.5, 0.75) - shader.bind() - shader.uniform_float("color", color) - batch = batch_for_shader(shader, "LINES", {"pos": [corners[i1], corners[i2]]}) - batch.draw(shader) - - # Draw the trihedron edges - closest_indices = find_closest_trihedron(corners, edges, region, rv3d) - for axis in "XYZ": - pair = closest_indices[axis] - if pair is not None: - i1, i2 = pair - color = axis_colors[axis] - shader.bind() - shader.uniform_float("color", color) - batch = batch_for_shader(shader, "LINES", {"pos": [corners[i1], corners[i2]]}) - batch.draw(shader) - - -def draw_dimension_text(): - from bpy_extras.view3d_utils import location_3d_to_region_2d - from bonsai.bim.module.drawing.helper import format_distance - - selected_objects = [obj for obj in bpy.context.selected_objects if hasattr(obj, "bound_box")] - if not selected_objects: - return - corners, edges, axis_colors = get_combined_bounding_box_corners(selected_objects) - if not corners: - return - - dims = mathutils.Vector( - ( - (corners[4] - corners[0]).length, - (corners[3] - corners[0]).length, - (corners[1] - corners[0]).length, - ) - ) - - region = bpy.context.region - rv3d = bpy.context.region_data - closest_indices = find_closest_trihedron(corners, edges, region, rv3d) - - font_id = 0 - blf.size(font_id, 20) - for axis in "XYZ": - pair = closest_indices[axis] - if pair is not None: - i1, i2 = pair - center = (corners[i1] + corners[i2]) / 2 - value = getattr(dims, axis.lower()) - screen_co = location_3d_to_region_2d(region, rv3d, center) - if screen_co is not None: - # Draw axis label in color - blf.position(font_id, screen_co.x, screen_co.y, 0) - blf.color(font_id, *axis_colors[axis]) - blf.draw(font_id, f"{axis}: ") - axis_width, _ = blf.dimensions(font_id, f"{axis}: ") - # Draw value+unit in white - blf.position(font_id, screen_co.x + axis_width, screen_co.y, 0) - blf.color(font_id, 1, 1, 1, 1) - value_str = format_distance(value, hide_units=False) - blf.draw(font_id, value_str) - - # To show the corners indexes, uncomment the following lines - # for idx, corner in enumerate(corners): - # screen_co = location_3d_to_region_2d(region, rv3d, corner) - # if screen_co is not None: - # blf.position(font_id, screen_co.x, screen_co.y, 0) - # blf.color(font_id, 1, 1, 0, 1) - # blf.draw(font_id, str(idx)) - - -def enable_bounding_box_wire_cube(): - global _draw_handler_box, _draw_handler_text - if _draw_handler_box is None: - _draw_handler_box = bpy.types.SpaceView3D.draw_handler_add( - draw_bounding_box_wire_cube, (), "WINDOW", "POST_VIEW" - ) - if _draw_handler_text is None: - _draw_handler_text = bpy.types.SpaceView3D.draw_handler_add(draw_dimension_text, (), "WINDOW", "POST_PIXEL") - - -def disable_bounding_box_wire_cube(): - global _draw_handler_box, _draw_handler_text - if _draw_handler_box is not None: - bpy.types.SpaceView3D.draw_handler_remove(_draw_handler_box, "WINDOW") - _draw_handler_box = None - if _draw_handler_text is not None: - bpy.types.SpaceView3D.draw_handler_remove(_draw_handler_text, "WINDOW") - _draw_handler_text = None - - class UIData: data = {} is_loaded = False @@ -709,9 +515,6 @@ class BIM_PT_derived_coordinates(Panel): def poll(cls, context): return context.active_object is not None - def update_show_colored_dimensions(self, context): - bpy.ops.bim.toggle_local_gizmo() - def draw(self, context): if not DerivedCoordinatesData.is_loaded: DerivedCoordinatesData.load() @@ -722,23 +525,19 @@ class BIM_PT_derived_coordinates(Panel): text += "*" row.operator("bim.edit_object_placement", text=text, icon="EXPORT") - # --- XYZ Dimensions with checkbox --- row = self.layout.row(align=True) row.label(text="XYZ Dimensions") - row.prop(context.scene, "show_colored_dimensions", text="Show colored dimensions") row = self.layout.row(align=True) row.enabled = False area_3d = next((area for area in context.screen.areas if area.type == "VIEW_3D"), None) space_3d = next((space for space in area_3d.spaces if space.type == "VIEW_3D"), None) - if context.scene.show_colored_dimensions: - enable_bounding_box_wire_cube() + if bpy.context.scene.MeasureToolSettings.measurement_type == "XYZ_DIMENSIONS": for axis, icon, idx in [("X", "STRIP_COLOR_01", 0), ("Y", "STRIP_COLOR_04", 1), ("Z", "STRIP_COLOR_05", 2)]: row.label(text="", icon=icon) row.prop(context.active_object, "dimensions", text=axis, index=idx) else: - disable_bounding_box_wire_cube() row.prop(context.active_object, "dimensions", text="X", index=0) row.prop(context.active_object, "dimensions", text="Y", index=1) row.prop(context.active_object, "dimensions", text="Z", index=2) diff --git a/src/bonsai/bonsai/bim/module/project/__init__.py b/src/bonsai/bonsai/bim/module/project/__init__.py index 81bf9e9ec7..0a03e0ca57 100644 --- a/src/bonsai/bonsai/bim/module/project/__init__.py +++ b/src/bonsai/bonsai/bim/module/project/__init__.py @@ -49,6 +49,7 @@ classes = ( operator.LoadProjectElements, operator.MeasureTool, operator.MeasureFaceAreaTool, + operator.MeasureXYZDimensionsTool, operator.ClearMeasurement, operator.NewProject, operator.QueryLinkedElement, @@ -100,6 +101,8 @@ def register(): bpy.types.TOPBAR_MT_file_import.append(ui.file_import_menu) bpy.types.TOPBAR_MT_file.prepend(ui.file_menu) bpy.types.TOPBAR_MT_file_context_menu.prepend(ui.file_menu) + bpy.types.Scene.prev_transform_orientation_slot_type = bpy.props.StringProperty(name="Previous Gizmo Orientation Type") + bpy.types.Scene.prev_show_gizmo_object_translate = bpy.props.BoolProperty(name="Previous Gizmo Translate") wm = bpy.context.window_manager if wm.keyconfigs.addon: km = wm.keyconfigs.addon.keymaps.get("Window") @@ -120,6 +123,8 @@ def unregister(): bpy.utils.unregister_tool(workspace.ExploreTool) del bpy.types.Scene.BIMProjectProperties del bpy.types.Scene.MeasureToolSettings + del bpy.types.Scene.prev_transform_orientation_slot_type + del bpy.types.Scene.prev_show_gizmo_object_translate bpy.app.handlers.load_post.remove(decorator.toggle_decorations_on_load) bpy.types.TOPBAR_MT_file.remove(ui.file_menu) bpy.types.TOPBAR_MT_file_context_menu.remove(ui.file_menu) diff --git a/src/bonsai/bonsai/bim/module/project/operator.py b/src/bonsai/bonsai/bim/module/project/operator.py index 5542ff24d4..c83e863ca6 100644 --- a/src/bonsai/bonsai/bim/module/project/operator.py +++ b/src/bonsai/bonsai/bim/module/project/operator.py @@ -25,6 +25,9 @@ import tempfile import traceback import subprocess import datetime +import blf +import mathutils +import gpu import ifcopenshell.api.attribute import numpy as np import ifcopenshell @@ -64,6 +67,7 @@ from bonsai.bim.module.project.prop import BreadcrumbType from bonsai.bim.module.model.decorator import PolylineDecorator, FaceAreaDecorator from bonsai.bim.module.model.polyline import PolylineOperator from typing import Union, TYPE_CHECKING, get_args, Literal +from gpu_extras.batch import batch_for_shader if TYPE_CHECKING: import bpy.stub_internal.rna_enums as rna_enums @@ -2787,6 +2791,278 @@ class MeasureFaceAreaTool(bpy.types.Operator, PolylineOperator): FaceAreaDecorator.install(context) return {"RUNNING_MODAL"} +_draw_handler_box = None +_draw_handler_text = None + + +def get_combined_bounding_box_corners(objects): + import mathutils + + if not objects: + return None, None, None + + if len(objects) == 1: + obj = bpy.context.active_object + corners = [obj.matrix_world @ mathutils.Vector(corner) for corner in obj.bound_box] + + else: + all_corners = [] + for obj in objects: + if hasattr(obj, "bound_box"): + all_corners.extend([obj.matrix_world @ mathutils.Vector(corner) for corner in obj.bound_box]) + + min_corner = mathutils.Vector( + (min(v.x for v in all_corners), min(v.y for v in all_corners), min(v.z for v in all_corners)) + ) + max_corner = mathutils.Vector( + (max(v.x for v in all_corners), max(v.y for v in all_corners), max(v.z for v in all_corners)) + ) + corners = [ + mathutils.Vector((min_corner.x, min_corner.y, min_corner.z)), + mathutils.Vector((min_corner.x, min_corner.y, max_corner.z)), + mathutils.Vector((min_corner.x, max_corner.y, max_corner.z)), + mathutils.Vector((min_corner.x, max_corner.y, min_corner.z)), + mathutils.Vector((max_corner.x, min_corner.y, min_corner.z)), + mathutils.Vector((max_corner.x, min_corner.y, max_corner.z)), + mathutils.Vector((max_corner.x, max_corner.y, max_corner.z)), + mathutils.Vector((max_corner.x, max_corner.y, min_corner.z)), + ] + + edges = [ + (0, 1, "Z"), + (1, 2, "Y"), + (2, 3, "Z"), + (3, 0, "Y"), + (4, 5, "Z"), + (5, 6, "Y"), + (6, 7, "Z"), + (7, 4, "Y"), + (0, 4, "X"), + (1, 5, "X"), + (2, 6, "X"), + (3, 7, "X"), + ] + axis_colors = { + "X": (0.956, 0.282, 0.322, 1), + "Y": (0.565, 0.812, 0.125, 1), + "Z": (0.196, 0.529, 0.929, 1), + } + return corners, edges, axis_colors + + +def find_closest_trihedron(corners, edges, region, rv3d): + from bpy_extras.view3d_utils import location_3d_to_region_2d + + # Project all corners to 2D and find the one with the lowest Y value + min_y = float("inf") + best_origin = None + for idx, corner in enumerate(corners): + screen_co = location_3d_to_region_2d(region, rv3d, corner) + if screen_co is not None and screen_co.y < min_y: + min_y = screen_co.y + best_origin = idx + + trihedron = [ + {"X": (0, 4), "Y": (0, 3), "Z": (0, 1)}, + {"X": (1, 5), "Y": (1, 2), "Z": (1, 0)}, + {"X": (2, 6), "Y": (2, 1), "Z": (2, 3)}, + {"X": (3, 7), "Y": (3, 0), "Z": (3, 2)}, + {"X": (4, 0), "Y": (4, 7), "Z": (4, 5)}, + {"X": (5, 1), "Y": (5, 6), "Z": (5, 4)}, + {"X": (6, 2), "Y": (6, 5), "Z": (6, 7)}, + {"X": (7, 3), "Y": (7, 4), "Z": (7, 6)}, + ] + + return trihedron[best_origin] + + +def draw_bounding_box_wire_cube(): + selected_objects = [obj for obj in bpy.context.selected_objects if hasattr(obj, "bound_box")] + if not selected_objects: + return + corners, edges, axis_colors = get_combined_bounding_box_corners(selected_objects) + region = bpy.context.region + rv3d = bpy.context.region_data + + shader = gpu.shader.from_builtin("UNIFORM_COLOR") + gpu.state.line_width_set(2.0) + + # Draw the bounding box edges + for i1, i2, axis in edges: + color = (0.5, 0.5, 0.5, 0.75) + shader.bind() + shader.uniform_float("color", color) + batch = batch_for_shader(shader, "LINES", {"pos": [corners[i1], corners[i2]]}) + batch.draw(shader) + + # Draw the trihedron edges + closest_indices = find_closest_trihedron(corners, edges, region, rv3d) + for axis in "XYZ": + pair = closest_indices[axis] + if pair is not None: + i1, i2 = pair + color = axis_colors[axis] + shader.bind() + shader.uniform_float("color", color) + batch = batch_for_shader(shader, "LINES", {"pos": [corners[i1], corners[i2]]}) + batch.draw(shader) + + +def draw_dimension_text(): + from bpy_extras.view3d_utils import location_3d_to_region_2d + from bonsai.bim.module.drawing.helper import format_distance + + selected_objects = [obj for obj in bpy.context.selected_objects if hasattr(obj, "bound_box")] + if not selected_objects: + return + corners, edges, axis_colors = get_combined_bounding_box_corners(selected_objects) + if not corners: + return + + dims = mathutils.Vector( + ( + (corners[4] - corners[0]).length, + (corners[3] - corners[0]).length, + (corners[1] - corners[0]).length, + ) + ) + + region = bpy.context.region + rv3d = bpy.context.region_data + closest_indices = find_closest_trihedron(corners, edges, region, rv3d) + + font_id = 0 + blf.size(font_id, 20) + for axis in "XYZ": + pair = closest_indices[axis] + if pair is not None: + i1, i2 = pair + center = (corners[i1] + corners[i2]) / 2 + value = getattr(dims, axis.lower()) + screen_co = location_3d_to_region_2d(region, rv3d, center) + if screen_co is not None: + # Draw axis label in color + blf.position(font_id, screen_co.x, screen_co.y, 0) + blf.color(font_id, *axis_colors[axis]) + blf.draw(font_id, f"{axis}: ") + axis_width, _ = blf.dimensions(font_id, f"{axis}: ") + # Draw value+unit in white + blf.position(font_id, screen_co.x + axis_width, screen_co.y, 0) + blf.color(font_id, 1, 1, 1, 1) + value_str = format_distance(value, hide_units=False) + blf.draw(font_id, value_str) + + # To show the corners indexes, uncomment the following lines + # for idx, corner in enumerate(corners): + # screen_co = location_3d_to_region_2d(region, rv3d, corner) + # if screen_co is not None: + # blf.position(font_id, screen_co.x, screen_co.y, 0) + # blf.color(font_id, 1, 1, 0, 1) + # blf.draw(font_id, str(idx)) + + +def enable_bounding_box_wire_cube(): + global _draw_handler_box, _draw_handler_text + if _draw_handler_box is None: + _draw_handler_box = bpy.types.SpaceView3D.draw_handler_add( + draw_bounding_box_wire_cube, (), "WINDOW", "POST_VIEW" + ) + if _draw_handler_text is None: + _draw_handler_text = bpy.types.SpaceView3D.draw_handler_add(draw_dimension_text, (), "WINDOW", "POST_PIXEL") + + +def disable_bounding_box_wire_cube(): + global _draw_handler_box, _draw_handler_text + if _draw_handler_box is not None: + bpy.types.SpaceView3D.draw_handler_remove(_draw_handler_box, "WINDOW") + _draw_handler_box = None + if _draw_handler_text is not None: + bpy.types.SpaceView3D.draw_handler_remove(_draw_handler_text, "WINDOW") + _draw_handler_text = None + + +previous_selection = set() + + +def selection_monitor_handler(scene, depsgraph): + global previous_selection + + # Get the current selection + current_selection = {obj.name for obj in scene.objects if obj.select_get()} + + # If selection changed, print and update + print("Selection changed:", current_selection) + if current_selection != previous_selection: + if len(bpy.context.selected_objects) == 1: + scene.transform_orientation_slots[1].type = "LOCAL" + else: + scene.transform_orientation_slots[1].type = "GLOBAL" + area = next((a for a in bpy.context.screen.areas if a.type == "VIEW_3D"), None) + space = next((s for s in area.spaces if s.type == "VIEW_3D"), None) + space.show_gizmo_object_translate = True + previous_selection = current_selection + + +def register_handler(): + unregister_handler() + bpy.app.handlers.depsgraph_update_post.append(selection_monitor_handler) + + +def unregister_handler(): + handlers = bpy.app.handlers.depsgraph_update_post + handlers[:] = [h for h in handlers if h.__name__ != "selection_monitor_handler"] + + + +class MeasureXYZDimensionsTool(bpy.types.Operator): + bl_idname = "bim.measure_xyz_dimensions_tool" + bl_label = "Show bonding box dimensions" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + + scene = bpy.context.scene + area_3d = next((area for area in bpy.context.screen.areas if area.type == "VIEW_3D"), None) + space_3d = next((space for space in area_3d.spaces if space.type == "VIEW_3D"), None) if area_3d else None + + try: + if bpy.context.scene.MeasureToolSettings.measurement_type == "XYZ_DIMENSIONS": + enable_bounding_box_wire_cube() + register_handler() + if not scene.get("prev_transform_orientation_slot_type", None): + scene["prev_transform_orientation_slot_type"] = scene.transform_orientation_slots[1].type + if not scene.get("prev_show_gizmo_object_translate", None): + scene["prev_show_gizmo_object_translate"] = space_3d.show_gizmo_object_translate if space_3d else False + + # Set orientation and gizmo based on selection count + if space_3d: + if len(context.selected_objects) == 1: + scene.transform_orientation_slots[1].type = "LOCAL" + else: + scene.transform_orientation_slots[1].type = "GLOBAL" + space_3d.show_gizmo_object_translate = True + + else: + disable_bounding_box_wire_cube() + unregister_handler() + # Restore previous state + if space_3d: + prev_orientation = scene.get("prev_transform_orientation_slot_type", None) + if prev_orientation: + scene.transform_orientation_slots[1].type = prev_orientation + scene["prev_transform_orientation_slot_type"] = "" + prev_gizmo = scene.get("prev_show_gizmo_object_translate", None) + if prev_gizmo is not None: + space_3d.show_gizmo_object_translate = prev_gizmo + scene["prev_show_gizmo_object_translate"] = None + + except Exception: + self.report({"WARNING"}, "Could not set transform orientation.") + return {"FINISHED"} + + + + class ClearMeasurement(bpy.types.Operator): bl_idname = "bim.clear_measurement" diff --git a/src/bonsai/bonsai/bim/module/project/prop.py b/src/bonsai/bonsai/bim/module/project/prop.py index 68f70a87d7..7afa5d5ca1 100644 --- a/src/bonsai/bonsai/bim/module/project/prop.py +++ b/src/bonsai/bonsai/bim/module/project/prop.py @@ -507,15 +507,20 @@ class BIMProjectProperties(PropertyGroup): yield link +def measurement_type_update(self, context): + print(f"Measurement type changed to: {self.measurement_type}") + bpy.ops.bim.measure_xyz_dimensions_tool() + class MeasureToolSettings(PropertyGroup): measurement_type_items = [ ("SINGLE", "SINGLE", "Single", "FIXED_SIZE", 1), ("POLYLINE", "POLYLINE", "Polyline", "DRIVER_ROTATIONAL_DIFFERENCE", 2), ("POLY_AREA", "POLY_AREA", "Poyline Area", "OUTLINER_DATA_LIGHTPROBE", 3), ("FACE_AREA", "FACE_AREA", "Face Area", "FACESEL", 4), + ("XYZ_DIMENSIONS", "XYZ_DIMENSIONS", "XYZ Dimensions", "ORIENTATION_LOCAL", 5), ] - measurement_type: bpy.props.EnumProperty(items=measurement_type_items, default="POLYLINE") + measurement_type: bpy.props.EnumProperty(items=measurement_type_items, default="POLYLINE", update= measurement_type_update) if TYPE_CHECKING: measurement_type: Literal["SINGLE", "POLYLINE", "AREA"] diff --git a/src/bonsai/bonsai/bim/module/project/workspace.py b/src/bonsai/bonsai/bim/module/project/workspace.py index d58ad55adb..29048ef726 100644 --- a/src/bonsai/bonsai/bim/module/project/workspace.py +++ b/src/bonsai/bonsai/bim/module/project/workspace.py @@ -108,5 +108,7 @@ class ExploreHotkey(bpy.types.Operator): measure_type = bpy.context.scene.MeasureToolSettings.measurement_type if measure_type == "FACE_AREA": bpy.ops.bim.measure_face_area_tool("INVOKE_DEFAULT") + elif measure_type == "XYZ_DIMENSIONS": + pass else: bpy.ops.bim.measure_tool("INVOKE_DEFAULT", measure_type=measure_type)