From 6b8024f83504c240cff172b7224bcf1c6af37b07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Perdig=C3=A3o?= Date: Thu, 9 Jan 2025 13:28:07 -0300 Subject: [PATCH] Refactor: Improve profile preview decorator to prevent GPU context errors --- .../bonsai/bim/module/model/__init__.py | 4 + .../bonsai/bim/module/model/decorator.py | 131 ++-------------- .../bonsai/bim/module/model/polyline.py | 148 ++++++++++++++++++ src/bonsai/bonsai/bim/module/model/profile.py | 2 + src/bonsai/bonsai/bim/module/model/prop.py | 10 ++ 5 files changed, 174 insertions(+), 121 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/model/__init__.py b/src/bonsai/bonsai/bim/module/model/__init__.py index 74dd0a2e7f..6bf42d99e9 100644 --- a/src/bonsai/bonsai/bim/module/model/__init__.py +++ b/src/bonsai/bonsai/bim/module/model/__init__.py @@ -128,6 +128,7 @@ classes = ( prop.SnapMousePoint, prop.PolylinePoint, prop.Polyline, + prop.ProductPreviewItem, prop.BIMModelProperties, prop.BIMArrayProperties, prop.BIMStairProperties, @@ -137,6 +138,7 @@ classes = ( prop.BIMRailingProperties, prop.BIMRoofProperties, prop.BIMPolylineProperties, + prop.BIMProductPreviewProperties, ui.BIM_PT_array, ui.BIM_PT_stair, ui.BIM_PT_sverchok, @@ -219,6 +221,7 @@ def register(): bpy.types.Scene.BIMModelProperties = bpy.props.PointerProperty(type=prop.BIMModelProperties) bpy.types.Scene.BIMPolylineProperties = bpy.props.PointerProperty(type=prop.BIMPolylineProperties) + bpy.types.Scene.BIMProductPreviewProperties = bpy.props.PointerProperty(type=prop.BIMProductPreviewProperties) bpy.types.Object.BIMArrayProperties = bpy.props.PointerProperty(type=prop.BIMArrayProperties) bpy.types.Object.BIMStairProperties = bpy.props.PointerProperty(type=prop.BIMStairProperties) bpy.types.Object.BIMSverchokProperties = bpy.props.PointerProperty(type=prop.BIMSverchokProperties) @@ -249,6 +252,7 @@ def unregister(): del bpy.types.Scene.BIMModelProperties del bpy.types.Scene.BIMPolylineProperties + del bpy.types.Scene.BIMProductPreviewProperties del bpy.types.Object.BIMArrayProperties del bpy.types.Object.BIMStairProperties del bpy.types.Object.BIMSverchokProperties diff --git a/src/bonsai/bonsai/bim/module/model/decorator.py b/src/bonsai/bonsai/bim/module/model/decorator.py index 301f2d5edb..5e3fc70e06 100644 --- a/src/bonsai/bonsai/bim/module/model/decorator.py +++ b/src/bonsai/bonsai/bim/module/model/decorator.py @@ -786,6 +786,10 @@ class ProductDecorator: @classmethod def uninstall(cls): + props = bpy.context.scene.BIMProductPreviewProperties # updated by model/polyline.py + props.verts.clear() + props.edges.clear() + props.tris.clear() for handler in cls.handlers: try: SpaceView3D.draw_handler_remove(handler, "WINDOW") @@ -978,127 +982,12 @@ class ProductDecorator: data["verts"] = [tuple(rot_mat @ (Vector((v[0], v[1], (v[2] + rl)))) + mouse_point) for v in data["verts"]] return data - def get_profile_preview_data(self, context, relating_type): - material = ifcopenshell.util.element.get_material(relating_type) - try: - profile_curve = material.MaterialProfiles[0].Profile - except: - return {} - - model_props = context.scene.BIMModelProperties - cardinal_point = model_props.cardinal_point - - polyline_verts = [] - polyline_data = context.scene.BIMPolylineProperties.insertion_polyline - polyline_points = polyline_data[0].polyline_points if polyline_data else [] - if len(polyline_points) < 2: - return - for point in polyline_points: - polyline_verts.append(Vector((point.x, point.y, point.z))) - polyline_edges = [(i, i+1) for i in range(len(polyline_verts)-1)] - settings = ifcopenshell.geom.settings() - settings.set("dimensionality", ifcopenshell.ifcopenshell_wrapper.CURVES_SURFACES_AND_SOLIDS) - shape = ifcopenshell.geom.create_shape(settings, profile_curve) - - verts = shape.verts - if not verts: - raise RuntimeError("Profile shape has no vertices, it probably is invalid.") - - edges = shape.edges - - grouped_verts = [[verts[i], verts[i + 1], 0] for i in range(0, len(verts), 3)] - grouped_edges = [[edges[i], edges[i + 1]] for i in range(0, len(edges), 2)] - - # Create offsets based on cardinal point - min_x = min(v[0] for v in grouped_verts) - max_x = max(v[0] for v in grouped_verts) - min_y = min(v[1] for v in grouped_verts) - max_y = max(v[1] for v in grouped_verts) - - x_offset = (max_x - min_x) / 2 - y_offset = (max_y - min_y) / 2 - - match cardinal_point: - case "1": - grouped_verts = [(v[0] - x_offset, v[1] + y_offset, v[2]) for v in grouped_verts] - case "2": - grouped_verts = [(v[0], v[1] + y_offset, v[2]) for v in grouped_verts] - case "3": - grouped_verts = [(v[0] + x_offset, v[1] + y_offset, v[2]) for v in grouped_verts] - case "4": - grouped_verts = [(v[0] - x_offset, v[1], v[2]) for v in grouped_verts] - case "5": - grouped_verts = [(v[0], v[1], v[2]) for v in grouped_verts] - case "6": - grouped_verts = [(v[0] + x_offset, v[1], v[2]) for v in grouped_verts] - case "7": - grouped_verts = [(v[0] - x_offset, v[1] - y_offset, v[2]) for v in grouped_verts] - case "8": - grouped_verts = [(v[0], v[1] - y_offset, v[2]) for v in grouped_verts] - case "9": - grouped_verts = [(v[0] + x_offset, v[1] - y_offset, v[2]) for v in grouped_verts] - - - scale_mat = Matrix.Scale(-1, 4, (1.0, 0.0, 0.0)) - grouped_verts = [scale_mat @ Vector(v) for v in grouped_verts] - profile_curve = bpy.data.curves.new("Profile", type='CURVE') - profile_curve.dimensions = "2D" - profile_curve.splines.new('POLY') - profile_curve.splines[0].points.add(len(grouped_verts) - 1) - - for i, point in enumerate(profile_curve.splines[0].points): - point.co = Vector((*grouped_verts[i], 0)) - - profile_obj = bpy.data.objects.new("Profile", profile_curve) - - preview_curve = bpy.data.curves.new("Polyline", type='CURVE') - preview_curve.dimensions = "2D" - preview_curve.splines.new('POLY') - preview_curve.splines[0].points.add(len(polyline_verts) - 1) - for i, point in enumerate(preview_curve.splines[0].points): - point.co = Vector((*polyline_verts[i], 0)) - preview_curve.splines[0].use_smooth = False - preview_curve.bevel_mode = "OBJECT" - preview_curve.bevel_object = profile_obj - - preview_obj = bpy.data.objects.new("Preview", preview_curve) - context.scene.collection.objects.link(preview_obj) - bpy.context.view_layer.objects.active = preview_obj - selection = preview_obj.select_get() - bpy.ops.object.select_all(action="DESELECT") - preview_obj.select_set(True) - bpy.ops.object.convert(target="MESH") - preview_obj = bpy.data.objects["Preview"] - - bm = bmesh.new() - new_verts = [bm.verts.new(v.co) for v in preview_obj.data.vertices] - index = [[v for v in edge.vertices] for edge in preview_obj.data.edges] - new_edges = [bm.edges.new((new_verts[i[0]], new_verts[i[1]])) for i in index] - for face in preview_obj.data.polygons: - verts = [new_verts[i] for i in face.vertices] - bm.faces.new(verts) - bm.verts.index_update() - bm.edges.index_update() - tris = [[loop.vert.index for loop in triangles] for triangles in bm.calc_loop_triangles()] - + def get_profile_preview_data(self, context): + props = context.scene.BIMProductPreviewProperties data = {} - data["verts"] = [tuple(v.co) for v in bm.verts] - data["edges"] = [(edge.verts[0].index, edge.verts[1].index) for edge in bm.edges] - data["tris"] = tris - - bpy.data.objects.remove(bpy.data.objects[preview_obj.name], do_unlink=True) - bpy.data.objects.remove(bpy.data.objects[profile_obj.name], do_unlink=True) - try: - bpy.data.curves.remove(profile_obj.data, do_unlink=True) - except: - pass - try: - bpy.data.curves.remove(preview_obj.data, do_unlink=True) - except: - pass - - bm.free() - + data["verts"] = [(*v.value,) for v in props.verts] + data["edges"] = [(int(e.tvalue[0]),int(e.tvalue[1])) for e in props.edges] + data["tris"] = [(int(t.value[0]), int(t.value[1]), int(t.value[2])) for t in props.tris] return data def draw_product_preview(self, context): @@ -1144,7 +1033,7 @@ class ProductDecorator: # Profile type products self.line_shader.uniform_float("lineWidth", 0.5) - product_preview_data = self.get_profile_preview_data(context, self.relating_type) + product_preview_data = self.get_profile_preview_data(context) if product_preview_data: self.draw_batch("LINES", product_preview_data["verts"], decorator_color, product_preview_data["edges"]) self.draw_batch( diff --git a/src/bonsai/bonsai/bim/module/model/polyline.py b/src/bonsai/bonsai/bim/module/model/polyline.py index 62c155ba98..168f0af08f 100644 --- a/src/bonsai/bonsai/bim/module/model/polyline.py +++ b/src/bonsai/bonsai/bim/module/model/polyline.py @@ -43,6 +43,150 @@ from typing import Optional, Union, Literal from lark import Lark, Transformer +def get_profile_preview_data(context, relating_type): + material = ifcopenshell.util.element.get_material(relating_type) + try: + profile_curve = material.MaterialProfiles[0].Profile + except: + return {} + + model_props = context.scene.BIMModelProperties + cardinal_point = model_props.cardinal_point + + polyline_verts = [] + polyline_data = context.scene.BIMPolylineProperties.insertion_polyline + polyline_points = polyline_data[0].polyline_points if polyline_data else [] + if len(polyline_points) < 2: + return + for point in polyline_points: + polyline_verts.append(Vector((point.x, point.y, point.z))) + polyline_edges = [(i, i+1) for i in range(len(polyline_verts)-1)] + + # Get profile shape + settings = ifcopenshell.geom.settings() + settings.set("dimensionality", ifcopenshell.ifcopenshell_wrapper.CURVES_SURFACES_AND_SOLIDS) + shape = ifcopenshell.geom.create_shape(settings, profile_curve) + + verts = shape.verts + if not verts: + raise RuntimeError("Profile shape has no vertices, it probably is invalid.") + + edges = shape.edges + + grouped_verts = [[verts[i], verts[i + 1], 0] for i in range(0, len(verts), 3)] + grouped_edges = [[edges[i], edges[i + 1]] for i in range(0, len(edges), 2)] + + # Create offsets based on cardinal point + min_x = min(v[0] for v in grouped_verts) + max_x = max(v[0] for v in grouped_verts) + min_y = min(v[1] for v in grouped_verts) + max_y = max(v[1] for v in grouped_verts) + + x_offset = (max_x - min_x) / 2 + y_offset = (max_y - min_y) / 2 + + match cardinal_point: + case "1": + grouped_verts = [(v[0] - x_offset, v[1] + y_offset, v[2]) for v in grouped_verts] + case "2": + grouped_verts = [(v[0], v[1] + y_offset, v[2]) for v in grouped_verts] + case "3": + grouped_verts = [(v[0] + x_offset, v[1] + y_offset, v[2]) for v in grouped_verts] + case "4": + grouped_verts = [(v[0] - x_offset, v[1], v[2]) for v in grouped_verts] + case "5": + grouped_verts = [(v[0], v[1], v[2]) for v in grouped_verts] + case "6": + grouped_verts = [(v[0] + x_offset, v[1], v[2]) for v in grouped_verts] + case "7": + grouped_verts = [(v[0] - x_offset, v[1] - y_offset, v[2]) for v in grouped_verts] + case "8": + grouped_verts = [(v[0], v[1] - y_offset, v[2]) for v in grouped_verts] + case "9": + grouped_verts = [(v[0] + x_offset, v[1] - y_offset, v[2]) for v in grouped_verts] + + + # Create profile curve + scale_mat = Matrix.Scale(-1, 4, (1.0, 0.0, 0.0)) + grouped_verts = [scale_mat @ Vector(v) for v in grouped_verts] + profile_curve = bpy.data.curves.new("Profile", type='CURVE') + profile_curve.dimensions = "2D" + profile_curve.splines.new('POLY') + profile_curve.splines[0].points.add(len(grouped_verts)) + + for i, point in enumerate(profile_curve.splines[0].points): + if i == len(grouped_verts): # Close curve + point.co = Vector((*grouped_verts[0], 0)) + continue + point.co = Vector((*grouped_verts[i], 0)) + profile_obj = bpy.data.objects.new("Profile", profile_curve) + + # Create path curve with profile object as bevel + path_curve = bpy.data.curves.new("Polyline", type='CURVE') + path_curve.dimensions = "2D" + path_curve.splines.new('POLY') + path_curve.splines[0].points.add(len(polyline_verts) - 1) + for i, point in enumerate(path_curve.splines[0].points): + point.co = Vector((*polyline_verts[i], 0)) + path_curve.splines[0].use_smooth = False + path_curve.bevel_mode = "OBJECT" + path_curve.bevel_object = profile_obj + + # Convert path curve to mesh + # This operation throws a warning when done during gpu drawing, so it was removed from the decorator file to be handled here + path_obj = bpy.data.objects.new("Preview", path_curve) + context.scene.collection.objects.link(path_obj) + bpy.context.view_layer.objects.active = path_obj + dg = context.evaluated_depsgraph_get() + path_obj = path_obj.evaluated_get(dg) + me = path_obj.to_mesh() + + # Create bmesh from path mesh + bm = bmesh.new() + new_verts = [bm.verts.new(v.co) for v in me.vertices] + index = [[v for v in edge.vertices] for edge in me.edges] + new_edges = [bm.edges.new((new_verts[i[0]], new_verts[i[1]])) for i in index] + for face in me.polygons: + verts = [new_verts[i] for i in face.vertices] + bm.faces.new(verts) + bm.verts.index_update() + bm.edges.index_update() + tris = [[loop.vert.index for loop in triangles] for triangles in bm.calc_loop_triangles()] + + bpy.data.objects.remove(bpy.data.objects[path_obj.name], do_unlink=True) + bpy.data.objects.remove(bpy.data.objects[profile_obj.name], do_unlink=True) + try: + bpy.data.curves.remove(profile_obj.data, do_unlink=True) + except: + pass + try: + bpy.data.curves.remove(path_obj.data, do_unlink=True) + except: + pass + + data = {} + data["verts"] = [tuple(v.co) for v in bm.verts] + data["edges"] = [(edge.verts[0].index, edge.verts[1].index) for edge in bm.edges] + data["tris"] = tris + + # Update properties so it can be used by the decorator + props = context.scene.BIMProductPreviewProperties + props.verts.clear() + props.edges.clear() + props.tris.clear() + + for vert in data["verts"]: + v = props.verts.add() + v.value = vert + for edge in data["edges"]: + e = props.edges.add() + e.tvalue = edge + for tri in data["tris"]: + t = props.tris.add() + t.value = tri + + bm.free() + class PolylineOperator: # TODO Fill doc strings """ """ @@ -387,6 +531,10 @@ class PolylineOperator: tool.Polyline.remove_last_polyline_point() tool.Blender.update_viewport() + def get_product_preview_data(self, context: bpy.types.Context, relating_type: ifcopenshell.entity_isntance): + if relating_type.is_a() in ["IfcBeamType", "IfcCoveringType", "IfcMemberType"]: + return get_profile_preview_data(context, relating_type) + def modal(self, context: bpy.types.Context, event: bpy.types.Event) -> Union[set[str], None]: PolylineDecorator.update(event, self.tool_state, self.input_ui, self.snapping_points[0]) tool.Blender.update_viewport() diff --git a/src/bonsai/bonsai/bim/module/model/profile.py b/src/bonsai/bonsai/bim/module/model/profile.py index 83fd9c97da..e9057b63f7 100644 --- a/src/bonsai/bonsai/bim/module/model/profile.py +++ b/src/bonsai/bonsai/bim/module/model/profile.py @@ -1207,6 +1207,8 @@ class DrawPolylineProfile(bpy.types.Operator, PolylineOperator): self.handle_inserting_polyline(context, event) + self.get_product_preview_data(context, self.relating_type) + cancel = self.handle_cancelation(context, event) if cancel is not None: ProductDecorator.uninstall() diff --git a/src/bonsai/bonsai/bim/module/model/prop.py b/src/bonsai/bonsai/bim/module/model/prop.py index 27aa3151a4..7809d5b2d1 100644 --- a/src/bonsai/bonsai/bim/module/model/prop.py +++ b/src/bonsai/bonsai/bim/module/model/prop.py @@ -820,3 +820,13 @@ class BIMPolylineProperties(PropertyGroup): snap_mouse_ref: bpy.props.CollectionProperty(type=SnapMousePoint) insertion_polyline: bpy.props.CollectionProperty(type=Polyline) measurement_polyline: bpy.props.CollectionProperty(type=Polyline) + +class ProductPreviewItem(PropertyGroup): + value: bpy.props.FloatVectorProperty() + tvalue: bpy.props.FloatVectorProperty(size=2) + +class BIMProductPreviewProperties(PropertyGroup): + verts: bpy.props.CollectionProperty(type=ProductPreviewItem) + edges: bpy.props.CollectionProperty(type=ProductPreviewItem) + tris: bpy.props.CollectionProperty(type=ProductPreviewItem) +