mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-10 17:58:20 +00:00
Add "Face Area" option to measure tool. See #6163.
This commit is contained in:
@@ -33,6 +33,7 @@ from gpu_extras.batch import batch_for_shader
|
||||
from gpu_extras.presets import draw_circle_2d
|
||||
from typing import Union
|
||||
from bonsai.bim.module.drawing.helper import format_distance
|
||||
from itertools import chain
|
||||
|
||||
|
||||
def transparent_color(color, alpha=0.1):
|
||||
@@ -320,14 +321,15 @@ class PolylineDecorator:
|
||||
relating_type = None
|
||||
|
||||
@classmethod
|
||||
def install(cls, context):
|
||||
def install(cls, context, ui_only=False):
|
||||
if cls.is_installed:
|
||||
cls.uninstall()
|
||||
handler = cls()
|
||||
cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw_snap_point, (context,), "WINDOW", "POST_PIXEL"))
|
||||
cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw_measurements, (context,), "WINDOW", "POST_PIXEL"))
|
||||
cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw_input_ui, (context,), "WINDOW", "POST_PIXEL"))
|
||||
cls.handlers.append(SpaceView3D.draw_handler_add(handler, (context,), "WINDOW", "POST_VIEW"))
|
||||
if not ui_only:
|
||||
cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw_snap_point, (context,), "WINDOW", "POST_PIXEL"))
|
||||
cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw_measurements, (context,), "WINDOW", "POST_PIXEL"))
|
||||
cls.handlers.append(SpaceView3D.draw_handler_add(handler, (context,), "WINDOW", "POST_VIEW"))
|
||||
cls.is_installed = True
|
||||
|
||||
@classmethod
|
||||
@@ -376,6 +378,7 @@ class PolylineDecorator:
|
||||
|
||||
return (x_axis, y_axis, z_axis), (x_middle, y_middle, z_middle)
|
||||
|
||||
@classmethod
|
||||
def calculate_polygon(self, points):
|
||||
bm = bmesh.new()
|
||||
|
||||
@@ -389,8 +392,8 @@ class PolylineDecorator:
|
||||
|
||||
bm.verts.index_update()
|
||||
bm.edges.index_update()
|
||||
verts = bm.verts
|
||||
edges = bm.edges
|
||||
verts = [v.co for v in bm.verts]
|
||||
edges = [[v.index for v in e.verts] for e in bm.edges]
|
||||
tris = [[loop.vert.index for loop in triangles] for triangles in bm.calc_loop_triangles()]
|
||||
|
||||
bm.free()
|
||||
@@ -970,3 +973,58 @@ class SlabDirectionDecorator:
|
||||
base = [obj.matrix_world @ Vector(d) for d in base]
|
||||
self.draw_batch("LINES", dir, selected_elements_color, [(0, 1), (1, 2), (1, 3)])
|
||||
self.draw_batch("LINES", base, selected_elements_color, [(0, 1)])
|
||||
|
||||
|
||||
class FaceAreaDecorator:
|
||||
is_installed = False
|
||||
handlers = []
|
||||
|
||||
@classmethod
|
||||
def install(cls, context):
|
||||
if cls.is_installed:
|
||||
cls.uninstall()
|
||||
handler = cls()
|
||||
cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw_face_area, (context,), "WINDOW", "POST_VIEW"))
|
||||
cls.is_installed = True
|
||||
|
||||
@classmethod
|
||||
def uninstall(cls):
|
||||
for handler in cls.handlers:
|
||||
try:
|
||||
SpaceView3D.draw_handler_remove(handler, "WINDOW")
|
||||
except ValueError:
|
||||
pass
|
||||
cls.is_installed = False
|
||||
|
||||
def draw_batch(self, shader_type, content_pos, color, indices=None):
|
||||
shader = self.line_shader if shader_type == "LINES" else self.shader
|
||||
batch = batch_for_shader(shader, shader_type, {"pos": content_pos}, indices=indices)
|
||||
shader.uniform_float("color", color)
|
||||
batch.draw(shader)
|
||||
|
||||
def draw_face_area(self, context):
|
||||
def transparent_color(color, alpha=0.1):
|
||||
color = [i for i in color]
|
||||
color[3] = alpha
|
||||
return color
|
||||
|
||||
self.addon_prefs = tool.Blender.get_addon_preferences()
|
||||
self.line_shader = gpu.shader.from_builtin("POLYLINE_UNIFORM_COLOR")
|
||||
self.line_shader.bind() # required to be able to change uniforms of the shader
|
||||
self.line_shader.uniform_float("viewportSize", (context.region.width, context.region.height))
|
||||
self.shader = gpu.shader.from_builtin("UNIFORM_COLOR")
|
||||
self.line_shader.uniform_float("lineWidth", 2.0)
|
||||
gpu.state.point_size_set(6)
|
||||
gpu.state.blend_set("ALPHA")
|
||||
decorator_color = self.addon_prefs.decorator_color_special
|
||||
|
||||
polyline_data = context.scene.BIMPolylineProperties.insertion_polyline
|
||||
for i, polyline in enumerate(polyline_data):
|
||||
vertices = []
|
||||
for point in polyline.polyline_points:
|
||||
vertices.append((point.x, point.y, point.z))
|
||||
data = PolylineDecorator.calculate_polygon(vertices)
|
||||
if data:
|
||||
self.draw_batch("POINTS", data["verts"], decorator_color)
|
||||
self.draw_batch("LINES", data["verts"], decorator_color, data["edges"])
|
||||
self.draw_batch("TRIS", data["verts"], transparent_color(decorator_color, alpha=0.5), data["tris"])
|
||||
|
||||
@@ -1119,6 +1119,7 @@ class PolylinePoint(PropertyGroup):
|
||||
|
||||
|
||||
class Polyline(PropertyGroup):
|
||||
id: bpy.props.StringProperty(name="Id")
|
||||
polyline_points: bpy.props.CollectionProperty(type=PolylinePoint)
|
||||
measurement_type: bpy.props.StringProperty(name="Measurement Type")
|
||||
area: bpy.props.StringProperty(name="Measured Area")
|
||||
|
||||
@@ -48,6 +48,7 @@ classes = (
|
||||
operator.LoadProject,
|
||||
operator.LoadProjectElements,
|
||||
operator.MeasureTool,
|
||||
operator.MeasureFaceAreaTool,
|
||||
operator.ClearMeasurement,
|
||||
operator.NewProject,
|
||||
operator.QueryLinkedElement,
|
||||
|
||||
@@ -61,7 +61,7 @@ from ifcopenshell.geom import ShapeElementType
|
||||
from bonsai.bim.module.project.data import LinksData, ProjectLibraryData
|
||||
from bonsai.bim.module.project.decorator import ProjectDecorator, ClippingPlaneDecorator, MeasureDecorator
|
||||
from bonsai.bim.module.project.prop import BreadcrumbType
|
||||
from bonsai.bim.module.model.decorator import PolylineDecorator
|
||||
from bonsai.bim.module.model.decorator import PolylineDecorator, FaceAreaDecorator
|
||||
from bonsai.bim.module.model.polyline import PolylineOperator
|
||||
from typing import Union, TYPE_CHECKING, Literal, get_args
|
||||
|
||||
@@ -2681,6 +2681,97 @@ class MeasureTool(bpy.types.Operator, PolylineOperator):
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class MeasureFaceAreaTool(bpy.types.Operator, PolylineOperator):
|
||||
bl_idname = "bim.measure_face_area_tool"
|
||||
bl_label = "Measure Face Area Tool"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
measure_type: bpy.props.StringProperty()
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.space_data.type == "VIEW_3D"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.input_options = ["AREA"]
|
||||
self.input_ui = tool.Polyline.create_input_ui(input_options=self.input_options)
|
||||
self.clicked_faces = []
|
||||
self.total_area = 0
|
||||
if tool.Ifc.get():
|
||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
else:
|
||||
self.unit_scale = tool.Blender.get_unit_scale()
|
||||
|
||||
def modal(self, context, event):
|
||||
def select_face(mouse_pos):
|
||||
objs_to_raycast = tool.Raycast.filter_objects_to_raycast(context, event, self.objs_2d_bbox)
|
||||
obj, _, face_index = tool.Raycast.cast_rays_and_get_best_object(
|
||||
context, event, objs_to_raycast, include_wireframes=False
|
||||
)
|
||||
if face_index:
|
||||
return obj, face_index
|
||||
return None, None
|
||||
|
||||
PolylineDecorator.update(event, self.tool_state, self.input_ui, self.snapping_points[0])
|
||||
tool.Blender.update_viewport()
|
||||
|
||||
if event.type in {"MIDDLEMOUSE", "WHEELUPMOUSE", "WHEELDOWNMOUSE"}:
|
||||
self.handle_mouse_move(context, event)
|
||||
return {"PASS_THROUGH"}
|
||||
|
||||
self.handle_mouse_move(context, event)
|
||||
|
||||
if event.value == "PRESS" and event.type == "LEFTMOUSE":
|
||||
tool.Blender.update_viewport()
|
||||
mouse_pos = event.mouse_region_x, event.mouse_region_y
|
||||
obj, face_index = select_face(mouse_pos)
|
||||
if face_index:
|
||||
if obj.data.polygons[face_index] not in self.clicked_faces:
|
||||
self.clicked_faces.append(obj.data.polygons[face_index])
|
||||
self.total_area += obj.data.polygons[face_index].area
|
||||
self.input_ui.set_value("AREA", self.total_area)
|
||||
polyline_data = bpy.context.scene.BIMPolylineProperties.insertion_polyline.add()
|
||||
polyline_data.id = obj.name + str(face_index)
|
||||
for v_id in obj.data.polygons[face_index].vertices:
|
||||
vertex = obj.matrix_world @ obj.data.vertices[v_id].co
|
||||
polyline_point = polyline_data.polyline_points.add()
|
||||
polyline_point.x = vertex.x
|
||||
polyline_point.y = vertex.y
|
||||
polyline_point.z = vertex.z
|
||||
tool.Blender.update_viewport()
|
||||
|
||||
if event.shift and (event.value == "PRESS" and event.type == "LEFTMOUSE"):
|
||||
mouse_pos = event.mouse_region_x, event.mouse_region_y
|
||||
obj, face_index = select_face(mouse_pos)
|
||||
if face_index:
|
||||
if obj.data.polygons[face_index] in self.clicked_faces:
|
||||
self.clicked_faces.remove(obj.data.polygons[face_index])
|
||||
self.total_area -= obj.data.polygons[face_index].area
|
||||
self.input_ui.set_value("AREA", self.total_area)
|
||||
polyline_data = bpy.context.scene.BIMPolylineProperties.insertion_polyline
|
||||
for i, polyline in enumerate(polyline_data):
|
||||
if polyline.id == obj.name + str(face_index):
|
||||
polyline_data.remove(i)
|
||||
tool.Blender.update_viewport()
|
||||
|
||||
if event.value == "RELEASE" and event.type in {"ESC", "RIGHTMOUSE"}:
|
||||
bpy.context.scene.BIMPolylineProperties.insertion_polyline.clear()
|
||||
PolylineDecorator.uninstall()
|
||||
FaceAreaDecorator.uninstall()
|
||||
tool.Blender.update_viewport()
|
||||
return {"CANCELLED"}
|
||||
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
super().invoke(context, event)
|
||||
PolylineDecorator.uninstall()
|
||||
PolylineDecorator.install(context, ui_only=True)
|
||||
FaceAreaDecorator.install(context)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class ClearMeasurement(bpy.types.Operator):
|
||||
bl_idname = "bim.clear_measurement"
|
||||
bl_label = "Clear measurement from the screen"
|
||||
|
||||
@@ -499,7 +499,8 @@ class MeasureToolSettings(PropertyGroup):
|
||||
measurement_type_items = [
|
||||
("SINGLE", "SINGLE", "Single", "FIXED_SIZE", 1),
|
||||
("POLYLINE", "POLYLINE", "Polyline", "DRIVER_ROTATIONAL_DIFFERENCE", 2),
|
||||
("AREA", "AREA", "Area", "OUTLINER_DATA_LIGHTPROBE", 3),
|
||||
("POLY_AREA", "POLY_AREA", "Poyline Area", "OUTLINER_DATA_LIGHTPROBE", 3),
|
||||
("FACE_AREA", "FACE_AREA", "Face Area", "FACESEL", 4),
|
||||
]
|
||||
|
||||
measurement_type: bpy.props.EnumProperty(items=measurement_type_items, default="POLYLINE")
|
||||
|
||||
@@ -106,4 +106,7 @@ class ExploreHotkey(bpy.types.Operator):
|
||||
for obj in tool.Blender.get_selected_objects():
|
||||
obj.select_set(False)
|
||||
measure_type = bpy.context.scene.MeasureToolSettings.measurement_type
|
||||
bpy.ops.bim.measure_tool("INVOKE_DEFAULT", measure_type=measure_type)
|
||||
if measure_type == "FACE_AREA":
|
||||
bpy.ops.bim.measure_face_area_tool("INVOKE_DEFAULT")
|
||||
else:
|
||||
bpy.ops.bim.measure_tool("INVOKE_DEFAULT", measure_type=measure_type)
|
||||
|
||||
@@ -66,6 +66,8 @@ class Polyline(bonsai.core.tool.Polyline):
|
||||
if attribute_name == "A":
|
||||
value = float(self.get_text_value(attribute_name))
|
||||
return f"{value:.2f}°"
|
||||
if attribute_name == "AREA":
|
||||
return Polyline.format_input_ui_units(value, True)
|
||||
else:
|
||||
return Polyline.format_input_ui_units(value)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user