updated to cope for multiple selection and better looking using trihedrons

This commit is contained in:
falken10
2025-05-15 20:38:31 +02:00
committed by Bruno Perdigão
parent 1213bbdd73
commit 8b4305360e
3 changed files with 248 additions and 54 deletions
@@ -77,7 +77,7 @@ classes = (
operator.UpdateItemAttributes,
operator.UpdateParametricRepresentation,
operator.UpdateRepresentation,
operator.BIM_OT_set_local_orientation,
operator.BIM_OT_local_coordinates_gizmo,
operator.CreateInstance,
prop.RepresentationItem,
prop.RepresentationItemObject,
@@ -135,7 +135,12 @@ 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", default=False, update=ui.BIM_PT_derived_coordinates.update_show_colored_dimensions)
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")
@@ -222,4 +227,4 @@ def unregister():
del bpy.types.Scene.show_colored_dimensions
del bpy.types.Scene._bonsai_prev_orientation_type
del bpy.types.Scene._bonsai_prev_gizmo_translate
del bpy.types.Scene._bonsai_prev_gizmo_translate
@@ -3470,30 +3470,79 @@ class CreateInstance(bpy.types.Operator, tool.Ifc.Operator):
return {"FINISHED"}
class BIM_OT_set_local_orientation(bpy.types.Operator):
bl_idname = "bim.set_local_orientation"
bl_label = "Set Local Orientation"
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
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:
scene.bonsai_prev_orientation_type = scene.transform_orientation_slots[1].type
scene.bonsai_prev_gizmo_translate = space_3d.show_gizmo_object_translate
scene.transform_orientation_slots[1].type = "LOCAL"
space_3d.show_gizmo_object_translate = True
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:
if hasattr(scene, "bonsai_prev_orientation_type"):
scene.transform_orientation_slots[1].type = scene.bonsai_prev_orientation_type
if hasattr(scene, "bonsai_prev_gizmo_translate"):
space_3d.show_gizmo_object_translate = scene.bonsai_prev_gizmo_translate
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'}
self.report({"WARNING"}, "Could not set transform orientation.")
return {"FINISHED"}
+176 -36
View File
@@ -21,6 +21,7 @@ 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
@@ -34,55 +35,195 @@ from bonsai.bim.module.geometry.data import (
from bonsai.bim.module.layer.data import LayersData
_draw_handler = None
_draw_handler_box = None
_draw_handler_text = None
def draw_bounding_box_wire_cube(context):
obj = context.active_object
if not obj or not hasattr(obj, "bound_box"):
return
corners = [obj.matrix_world @ mathutils.Vector(corner) for corner in obj.bound_box]
red = (0.9568627450980393, 0.2823529411764706, 0.3215686274509804, 1)
blue = (0.19607843137254902, 0.5294117647058824,0.9294117647058824 , 1)
green = (0.5647058823529412, 0.8117647058823529, 0.12549019607843137, 1)
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 = [
# X edges (red)
(0, 4, red), (1, 5, red),
(2, 6, red), (3, 7, red),
# Y edges (green)
(0, 3, green), (1, 2, green),
(4, 7, green), (5, 6, green),
# Z edges (blue)
(0, 1, blue), (2, 3, blue),
(4, 5, blue), (6, 7, blue),
(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]
shader = gpu.shader.from_builtin('UNIFORM_COLOR')
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)
for i1, i2, color in edges:
# 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 = batch_for_shader(shader, "LINES", {"pos": [corners[i1], corners[i2]]})
batch.draw(shader)
def draw_callback_px(self, context):
draw_bounding_box_wire_cube(context)
# 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
if _draw_handler is None:
_draw_handler = bpy.types.SpaceView3D.draw_handler_add(
draw_callback_px, (None, bpy.context), 'WINDOW', 'POST_VIEW'
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
if _draw_handler is not None:
bpy.types.SpaceView3D.draw_handler_remove(_draw_handler, 'WINDOW')
_draw_handler = None
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 = {}
@@ -569,7 +710,7 @@ class BIM_PT_derived_coordinates(Panel):
return context.active_object is not None
def update_show_colored_dimensions(self, context):
bpy.ops.bim.set_local_orientation()
bpy.ops.bim.toggle_local_gizmo()
def draw(self, context):
if not DerivedCoordinatesData.is_loaded:
@@ -581,7 +722,6 @@ 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")
@@ -589,12 +729,12 @@ class BIM_PT_derived_coordinates(Panel):
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)
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()
for axis, icon, idx in [("X", 'STRIP_COLOR_01', 0), ("Y", 'STRIP_COLOR_04', 1), ("Z", 'STRIP_COLOR_05', 2)]:
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: