From fd7a7d44c30a5b92d173768966372d6e01f75da6 Mon Sep 17 00:00:00 2001 From: Gorgious56 Date: Fri, 3 Jan 2025 16:12:51 +0100 Subject: [PATCH] Special geometry data on meshes are also loaded as geometry attributes. No functional change apart from in dissolve_triangulated_edges where it will try to load the attribute if it can. I believe it will be easier in the long term to use the builtin Attributes system. Unfortunately Curves don't support Attributes yet. Also, attributes are more lightweight compared to custom properties. --- src/bonsai/bonsai/tool/blender.py | 24 ++++++++++++++++++++++++ src/bonsai/bonsai/tool/geometry.py | 12 ++++++++++-- src/bonsai/bonsai/tool/loader.py | 25 +++++++++++-------------- 3 files changed, 45 insertions(+), 16 deletions(-) diff --git a/src/bonsai/bonsai/tool/blender.py b/src/bonsai/bonsai/tool/blender.py index cefbfd4d96..ee10c6dc76 100644 --- a/src/bonsai/bonsai/tool/blender.py +++ b/src/bonsai/bonsai/tool/blender.py @@ -1032,6 +1032,30 @@ class Blender(bonsai.core.tool.Blender): if child_obj: yield child_obj + class Attribute: + @classmethod + def fill_attribute(cls, data: bpy.types.ID, attribute_name: str, domain: str, data_type: str, values): + attribute = cls.ensure_attribute(data, attribute_name, domain, data_type) + attribute.data.foreach_set(cls.get_data_name(data_type), values) + + @classmethod + def ensure_attribute(cls, data: bpy.types.ID, attribute_name: str, domain: str, data_type: str): + attribute = data.attributes.get(attribute_name) + if not attribute: + attribute = data.attributes.new(attribute_name, domain=domain, type=data_type) + return attribute + + @classmethod + def get_data_name(cls, data_type: str): + if data_type in ("FLOAT", "INT", "BOOLEAN", "STRING"): + return "value" + if data_type.endswith("VECTOR"): + return "vector" + elif data_type.endswith("COLOR"): + return "color" + else: + raise NotImplementedError(f"Attribute data type `{data_type}` not implemented yet") + @classmethod def get_last_commit_hash(cls) -> Union[str, None]: """Get 8 symbols of last commit hash if it's present or return None otherwise.""" diff --git a/src/bonsai/bonsai/tool/geometry.py b/src/bonsai/bonsai/tool/geometry.py index d98786a6ba..02b26fe40c 100644 --- a/src/bonsai/bonsai/tool/geometry.py +++ b/src/bonsai/bonsai/tool/geometry.py @@ -261,9 +261,17 @@ class Geometry(bonsai.core.tool.Geometry): mesh_element.is_a("IfcShapeRepresentation") and ifcopenshell.util.representation.resolve_representation(mesh_element).RepresentationType == "AdvancedBrep" - ) or mesh_element.is_a("IfcAdvancedBrep"): + ) or mesh_element.is_a("IfcAdvancedBrep") or not obj.data: return - if obj.data and "ios_edges" in obj.data: + if hasattr(obj.data, "attributes") and (ios_edges_attribute := obj.data.attributes.get("ios_edges")): + # Edges from a forced triangulation are stored as True in a boolean attribute on the mesh + bm = bmesh.new() + bm.from_mesh(obj.data) + edges_to_dissolve = [e for i, e in enumerate(bm.edges) if not ios_edges_attribute.data[i].value] + bmesh.ops.dissolve_edges(bm, edges=edges_to_dissolve) + bm.to_mesh(obj.data) + bm.free() + elif "ios_edges" in obj.data: bm = bmesh.new() bm.from_mesh(obj.data) edges_to_keep = set(map(frozenset, obj.data["ios_edges"])) diff --git a/src/bonsai/bonsai/tool/loader.py b/src/bonsai/bonsai/tool/loader.py index 745322f059..bcfa7b66cf 100644 --- a/src/bonsai/bonsai/tool/loader.py +++ b/src/bonsai/bonsai/tool/loader.py @@ -939,7 +939,8 @@ class Loader(bonsai.core.tool.Loader): # we do `.tolist()` because Blender can't assign `np.int32` to it's custom attributes ios_edges = list(set(tuple(e) for e in ifcopenshell.util.shape.get_edges(geometry).tolist())) mesh["ios_edges"] = ios_edges - mesh["ios_item_ids"] = ifcopenshell.util.shape.get_faces_representation_item_ids(geometry).tolist() + ios_item_ids = ifcopenshell.util.shape.get_faces_representation_item_ids(geometry).tolist() + mesh["ios_item_ids"] = ios_item_ids mesh.vertices.add(num_vertices) mesh.vertices.foreach_set("co", verts) @@ -981,19 +982,13 @@ class Loader(bonsai.core.tool.Loader): if rep.is_a("IfcShapeRepresentation"): tool.Loader.load_indexed_colour_map(rep, mesh) - ios_edges_indices = [(e[0], e[1]) for e in ios_edges] - attribute_ios_edges = mesh.attributes.get("ios_edges") - if not attribute_ios_edges: - attribute_ios_edges = mesh.attributes.new("ios_edges", domain="EDGE", type="BOOLEAN") - - attribute_ios_edges.data.foreach_set( - "value", - [ - (e.vertices[0], e.vertices[1]) in ios_edges_indices - or (e.vertices[1], e.vertices[0]) in ios_edges_indices - for e in mesh.edges - ], - ) + ios_edges_values = [ + (e.vertices[0], e.vertices[1]) in ios_edges + or (e.vertices[1], e.vertices[0]) in ios_edges + for e in mesh.edges + ] + tool.Blender.Attribute.fill_attribute(mesh, "ios_edges", "EDGE", "BOOLEAN", ios_edges_values) + tool.Blender.Attribute.fill_attribute(mesh, "ios_item_ids", "FACE", "INT", ios_item_ids) else: e = geometry.edges v = verts @@ -1006,9 +1001,11 @@ class Loader(bonsai.core.tool.Loader): except AttributeError: edges_item_ids = [] mesh["ios_edges_item_ids"] = edges_item_ids + tool.Blender.Attribute.fill_attribute(mesh, "ios_edges_item_ids", "EDGE", "INT", edges_item_ids) mesh["ios_materials"] = [m.instance_id() for m in geometry.materials] mesh["ios_material_ids"] = geometry.material_ids + tool.Blender.Attribute.fill_attribute(mesh, "ios_material_ids", "FACE", "INT", geometry.material_ids) return mesh @classmethod