From bde39c12359fe761ef5fe0c128b8029bc8bdd21a Mon Sep 17 00:00:00 2001 From: Petru Conduraru Date: Thu, 16 Jul 2026 16:52:36 +0300 Subject: [PATCH] Bonsai: fix BCF viewpoint line markup crash on modern Grease Pencil API draw_lines() used the pre-4.3 Annotation Grease Pencil scripting API (frame.strokes.new()), which Blender removed the stroke-mutation methods from in 4.3 and dropped scene.grease_pencil/the legacy datablock entirely in 5.1. Activating a BCF viewpoint whose markup includes lines then crashed with AttributeError: bpy_prop_collection: attribute "new" not found. Detect the available API by capability (frame.drawing presence) rather than a hardcoded version check, since Blender exposes the rewritten, object-based Grease Pencil through grease_pencils_v3 on 4.3-5.0 and through the unified grease_pencils on 5.1+, while 4.2 still has the original Annotation API. Reproduced live against the reporter's actual BCF/IFC files on Blender 4.5.8 and 5.1.2: the crash occurs on unpatched code, and no crash occurs after the fix, with the viewpoint markup lines correctly drawn on both. Fixes #7318. Generated with the assistance of an AI coding tool. --- src/bonsai/bonsai/bim/module/bcf/operator.py | 68 ++++++++++++++------ 1 file changed, 50 insertions(+), 18 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/bcf/operator.py b/src/bonsai/bonsai/bim/module/bcf/operator.py index 461321852d..f3d1c202fc 100644 --- a/src/bonsai/bonsai/bim/module/bcf/operator.py +++ b/src/bonsai/bonsai/bim/module/bcf/operator.py @@ -1259,9 +1259,7 @@ class ActivateBcfViewpoint(bpy.types.Operator): if self.file: self.set_viewpoint_components(viewpoint, context) - gp = bpy.data.grease_pencils.get("BCF") - if gp: - bpy.data.grease_pencils.remove(gp) + self.delete_lines() if viewpoint.visualization_info.lines: self.draw_lines(viewpoint, context) @@ -1434,24 +1432,58 @@ class ActivateBcfViewpoint(bpy.types.Operator): if obj: obj.color = self.hex_to_rgb(color) + def delete_lines(self) -> None: + if obj := bpy.data.objects.get("BCF"): + bpy.data.objects.remove(obj) + for grease_pencils in (bpy.data.grease_pencils, getattr(bpy.data, "grease_pencils_v3", None)): + if grease_pencils is not None and (gp := grease_pencils.get("BCF")): + grease_pencils.remove(gp) + def draw_lines(self, viewpoint: bcf.agnostic.visinfo.VisualizationInfoHandler, context: bpy.types.Context) -> None: - gp = bpy.data.grease_pencils.new("BCF") - scene = context.scene - scene.grease_pencil = gp - scene.frame_set(1) + # Blender 4.3+ moved the object-based Grease Pencil to its own + # `grease_pencils_v3` collection (merged back into `grease_pencils` + # in 5.1+, which also dropped the legacy Annotation API used below). + grease_pencils = getattr(bpy.data, "grease_pencils_v3", None) + if grease_pencils is None: + grease_pencils = bpy.data.grease_pencils + gp = grease_pencils.new("BCF") layer = gp.layers.new("BCF Annotation", set_active=True) - layer.thickness = 3 - layer.color = (1, 0, 0) frame = layer.frames.new(1) - stroke = frame.strokes.new() - stroke.display_mode = "3DSPACE" - stroke.points.add(len(viewpoint.visualization_info.lines.line) * 2) - coords = [] - for l in viewpoint.visualization_info.lines.line: - coords.extend( - [l.start_point.x, l.start_point.y, l.start_point.z, l.end_point.x, l.end_point.y, l.end_point.z] - ) - stroke.points.foreach_set("co", coords) + lines = viewpoint.visualization_info.lines.line + if (drawing := getattr(frame, "drawing", None)) is not None: + obj = bpy.data.objects.new("BCF", gp) + context.scene.collection.objects.link(obj) + drawing.add_strokes(sizes=[2] * len(lines)) + coords = [] + for l in lines: + coords.extend( + [l.start_point.x, l.start_point.y, l.start_point.z, l.end_point.x, l.end_point.y, l.end_point.z] + ) + drawing.attributes["position"].data.foreach_set("vector", coords) + radius = drawing.attributes.new("radius", "FLOAT", "POINT") + radius.data.foreach_set("value", [0.01] * (len(lines) * 2)) + drawing.tag_positions_changed() + mat = bpy.data.materials.get("BCF Annotation") + if mat is None: + mat = bpy.data.materials.new("BCF Annotation") + bpy.data.materials.create_gpencil_data(mat) + mat.grease_pencil.color = (1, 0, 0, 1) + gp.materials.append(mat) + else: + scene = context.scene + scene.grease_pencil = gp + scene.frame_set(1) + layer.thickness = 3 + layer.color = (1, 0, 0) + stroke = frame.strokes.new() + stroke.display_mode = "3DSPACE" + stroke.points.add(len(lines) * 2) + coords = [] + for l in lines: + coords.extend( + [l.start_point.x, l.start_point.y, l.start_point.z, l.end_point.x, l.end_point.y, l.end_point.z] + ) + stroke.points.foreach_set("co", coords) def create_clipping_planes(self, viewpoint: bcf.agnostic.visinfo.VisualizationInfoHandler) -> None: n = 0