From 3077687c59368d04e0fab0293fd9faabe3614da5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Perdig=C3=A3o?= Date: Tue, 22 Oct 2024 10:01:50 -0300 Subject: [PATCH] Move product preview logic to its own decorator. --- .../bonsai/bim/module/model/decorator.py | 742 +++++++++--------- src/bonsai/bonsai/bim/module/model/product.py | 4 +- src/bonsai/bonsai/bim/module/model/wall.py | 5 +- 3 files changed, 394 insertions(+), 357 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/model/decorator.py b/src/bonsai/bonsai/bim/module/model/decorator.py index 0763a58465..1aa569b427 100644 --- a/src/bonsai/bonsai/bim/module/model/decorator.py +++ b/src/bonsai/bonsai/bim/module/model/decorator.py @@ -323,9 +323,6 @@ class PolylineDecorator: 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.draw_product_preview, (context,), "WINDOW", "POST_VIEW") - ) cls.handlers.append(SpaceView3D.draw_handler_add(handler, (context,), "WINDOW", "POST_VIEW")) cls.is_installed = True @@ -402,358 +399,6 @@ class PolylineDecorator: shader.uniform_float("color", color) batch.draw(shader) - def get_wall_preview_data(cls, context, relating_type): - def create_bmesh_from_vertices(vertices): - bm = bmesh.new() - - new_verts = [bm.verts.new(v) for v in polyline_vertices] - if is_closed: - new_edges = [bm.edges.new((new_verts[i], new_verts[i + 1])) for i in range(len(new_verts) - 1)] - new_edges.append( - bm.edges.new((new_verts[-1], new_verts[0])) - ) # Add an edge between the last an first point to make it closed. - else: - new_edges = [bm.edges.new((new_verts[i], new_verts[i + 1])) for i in range(len(new_verts) - 1)] - - bm.verts.index_update() - bm.edges.index_update() - return bm - - # Get properties from object type - layers = tool.Model.get_material_layer_parameters(relating_type) - if not layers["thickness"]: - return - thickness = layers["thickness"] - model_props = context.scene.BIMModelProperties - direction_sense = model_props.direction_sense - direction = 1 - if direction_sense == "NEGATIVE": - direction = -1 - offset_type = model_props.offset_type - offset = 0 - if offset_type == "CENTER": - offset = -thickness / 2 - elif offset_type == "INTERIOR": - offset = -thickness - - unit_system = tool.Drawing.get_unit_system() - factor = 1 - if unit_system == "IMPERIAL": - factor = 3.048 - if unit_system == "METRIC": - unit_length = context.scene.unit_settings.length_unit - if unit_length == "MILLIMETERS": - factor = 1000 - - # For the model properties, the offset value should just be converted - # However, for the wall preview logic that follows, offset and thickness must change direction - model_props.offset = offset * factor - thickness *= direction - offset *= direction - - height = float(model_props.extrusion_depth) - rl = float(model_props.rl1) - x_angle = float(model_props.x_angle) - angle_distortion = height * tan(x_angle) - - wall_preview_data = {} - wall_preview_data["verts"] = [] - - # Verts - polyline_vertices = [] - polyline_data = context.scene.BIMPolylineProperties.insertion_polyline - polyline_points = polyline_data[0].polyline_points if polyline_data else [] - if len(polyline_points) < 2: - wall_preview_data = [] - return - for point in polyline_points: - polyline_vertices.append(Vector((point.x, point.y, point.z))) - - is_closed = False - if ( - polyline_vertices[0].x == polyline_vertices[-1].x - and polyline_vertices[0].y == polyline_vertices[-1].y - and polyline_vertices[0].z == polyline_vertices[-1].z - ): - is_closed = True - polyline_vertices.pop(-1) # Remove the last point. The edges are going to inform that the shape is closed. - - bm_base = create_bmesh_from_vertices(polyline_vertices) - base_vertices = tool.Cad.offset_edges(bm_base, offset) - offset_base_verts = tool.Cad.offset_edges(bm_base, thickness + offset) - top_vertices = tool.Cad.offset_edges(bm_base, angle_distortion + offset) - offset_top_verts = tool.Cad.offset_edges(bm_base, angle_distortion + thickness + offset) - if is_closed: - base_vertices.append(base_vertices[0]) - offset_base_verts.append(offset_base_verts[0]) - top_vertices.append(top_vertices[0]) - offset_top_verts.append(offset_top_verts[0]) - - if offset_base_verts is not None: - for v in base_vertices: - wall_preview_data["verts"].append((v.co.x, v.co.y, v.co.z + rl)) - - for v in offset_base_verts[::-1]: - wall_preview_data["verts"].append((v.co.x, v.co.y, v.co.z + rl)) - - for v in top_vertices: - wall_preview_data["verts"].append((v.co.x, v.co.y, v.co.z + rl + height)) - - for v in offset_top_verts[::-1]: - wall_preview_data["verts"].append((v.co.x, v.co.y, v.co.z + rl + height)) - - bm_base.free() - - # Edges and Tris - points = [] - side_edges_1 = [] - side_edges_2 = [] - base_edges = [] - - for i in range(len(wall_preview_data["verts"])): - points.append(Vector(wall_preview_data["verts"][i])) - - n = len(points) // 2 - bottom_side_1 = [[i, (i + 1) % (n)] for i in range((n - 1) // 2)] - bottom_side_2 = [[i, (i + 1) % (n)] for i in range(n // 2, n - 1)] - bottom_connections = [[i, n - i - 1] for i in range(n // 2)] - bottom_loop = bottom_connections + bottom_side_1 + bottom_side_2 - side_edges_1.extend(bottom_side_1) - side_edges_2.extend(bottom_side_2) - base_edges.extend(bottom_loop) - - upper_side_1 = [[i + n for i in edges] for edges in bottom_side_1] - upper_side_2 = [[i + n for i in edges] for edges in bottom_side_2] - upper_loop = [[i + n for i in edges] for edges in bottom_loop] - side_edges_1.extend(upper_side_1) - side_edges_2.extend(upper_side_2) - base_edges.extend(upper_loop) - - loops = [side_edges_1, side_edges_2, base_edges] - - wall_preview_data["edges"] = [] - wall_preview_data["tris"] = [] - for i, group in enumerate(loops): - bm = bmesh.new() - - new_verts = [bm.verts.new(v) for v in points] - new_edges = [bm.edges.new((new_verts[e[0]], new_verts[e[1]])) for e in group] - - bm.verts.index_update() - bm.edges.index_update() - - if i == 2: - new_faces = bmesh.ops.contextual_create(bm, geom=bm.edges) - new_faces = bmesh.ops.bridge_loops(bm, edges=bm.edges, use_pairs=True, use_cyclic=True) - - bm.verts.index_update() - bm.edges.index_update() - edges = [[v.index for v in e.verts] for e in bm.edges] - tris = [[l.vert.index for l in loop] for loop in bm.calc_loop_triangles()] - wall_preview_data["edges"].extend(edges) - wall_preview_data["tris"].extend(tris) - - wall_preview_data["edges"] = list(set(tuple(e) for e in wall_preview_data["edges"])) - wall_preview_data["tris"] = list(set(tuple(t) for t in wall_preview_data["tris"])) - - return wall_preview_data - - def get_product_preview_data(cls, context, relating_type): - model_props = context.scene.BIMModelProperties - if relating_type.is_a("IfcDoorType"): - rl = float(model_props.rl1) - elif relating_type.is_a("IfcWindowType"): - rl = float(model_props.rl2) - else: - rl = 0 - snap_prop = context.scene.BIMPolylineProperties.snap_mouse_point[0] - mouse_point = Vector((snap_prop.x, snap_prop.y, snap_prop.z)) - snap_obj = bpy.data.objects.get(snap_prop.snap_object) - snap_element = tool.Ifc.get_entity(snap_obj) - rot_mat = Quaternion() - if snap_element and snap_element.is_a("IfcWall"): - rot_mat = snap_obj.matrix_world.to_quaternion() - - obj_type = tool.Ifc.get_object(relating_type) - if obj_type.data: - data = ItemDecorator.get_obj_data(obj_type) - 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) - profile = material.MaterialProfiles[0].Profile - model_props = context.scene.BIMModelProperties - extrusion_depth = model_props.extrusion_depth - cardinal_point = model_props.cardinal_point - rot_mat = Quaternion() - if relating_type.is_a("IfcBeamType"): - y_rot = Quaternion((0.0, 1.0, 0.0), radians(90)) - z_rot = Quaternion((0.0, 0.0, 1.0), radians(90)) - rot_mat = y_rot @ z_rot - # Get profile data - settings = ifcopenshell.geom.settings() - settings.set("dimensionality", ifcopenshell.ifcopenshell_wrapper.CURVES_SURFACES_AND_SOLIDS) - shape = ifcopenshell.geom.create_shape(settings, profile) - - 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 extrusion bmesh - bm = bmesh.new() - - new_verts = [bm.verts.new(v) for v in grouped_verts] - new_edges = [bm.edges.new((new_verts[i], new_verts[i + 1])) for i in range(len(grouped_verts) - 1)] - - bm.verts.index_update() - bm.edges.index_update() - - new_faces = bmesh.ops.contextual_create(bm, geom=bm.edges) - - new_faces = bmesh.ops.extrude_face_region(bm, geom=bm.faces) - new_verts = [e for e in new_faces["geom"] if isinstance(e, bmesh.types.BMVert)] - new_faces = bmesh.ops.translate(bm, verts=new_verts, vec=(0.0, 0.0, extrusion_depth)) - - bm.verts.index_update() - bm.edges.index_update() - tris = [[loop.vert.index for loop in triangles] for triangles in bm.calc_loop_triangles()] - - # Create bounding box - verts = bm.verts - i = len(verts) - - min_x = min(v.co.x for v in verts) - max_x = max(v.co.x for v in verts) - min_y = min(v.co.y for v in verts) - max_y = max(v.co.y for v in verts) - min_z = min(v.co.z for v in verts) - max_z = max(v.co.z for v in verts) - - bbox_verts = [ - (min_x, min_y, min_z), - (max_x, min_y, min_z), - (max_x, max_y, min_z), - (min_x, max_y, min_z), - (min_x, min_y, max_z), - (max_x, min_y, max_z), - (max_x, max_y, max_z), - (min_x, max_y, max_z), - ] - - bbox_edges = [ - (0 + i, 3 + i), - (3 + i, 7 + i), - (7 + i, 4 + i), - (4 + i, 0 + i), - (0 + i, 1 + i), - (3 + i, 2 + i), - (7 + i, 6 + i), - (4 + i, 5 + i), - (1 + i, 2 + i), - (2 + i, 6 + i), - (6 + i, 5 + i), - (5 + i, 1 + i), - ] - - # Calculate rotation, mouse position, angle and cardinal point - # TODO Angle - snap_prop = context.scene.BIMPolylineProperties.snap_mouse_point[0] - mouse_point = Vector((snap_prop.x, snap_prop.y, snap_prop.z)) - data = {} - - verts = [tuple(v.co) for v in verts] - verts.extend(bbox_verts) - verts = [tuple(rot_mat @ Vector(v)) for v in verts] - verts = [tuple(Vector(v) + mouse_point) for v in verts] - data["verts"] = verts - data["edges"] = bbox_edges - # data["edges"] = [(edge.verts[0].index, edge.verts[1].index) for edge in bm.edges] - data["tris"] = tris - - bm.free() - return data - - def draw_product_preview(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) - decorator_color = self.addon_prefs.decorator_color_special - polyline_data = context.scene.BIMPolylineProperties.insertion_polyline - polyline_points = polyline_data[0].polyline_points if polyline_data else [] - - self.relating_type = None - props = context.scene.BIMModelProperties - relating_type_id = props.relating_type_id - if relating_type_id: - self.relating_type = tool.Ifc.get().by_id(int(relating_type_id)) - - # Wall - wall_preview_data = self.get_wall_preview_data(context, self.relating_type) - if wall_preview_data: - self.draw_batch("LINES", wall_preview_data["verts"], decorator_color, wall_preview_data["edges"]) - self.draw_batch( - "TRIS", wall_preview_data["verts"], transparent_color(decorator_color), wall_preview_data["tris"] - ) - - # Mesh type products - product_preview_data = self.get_product_preview_data(context, self.relating_type) - if product_preview_data: - self.draw_batch("LINES", product_preview_data["verts"], decorator_color, product_preview_data["edges"]) - self.draw_batch( - "TRIS", product_preview_data["verts"], transparent_color(decorator_color), product_preview_data["tris"] - ) - - # Profile type products - product_preview_data = self.get_profile_preview_data(context, self.relating_type) - if product_preview_data: - self.draw_batch("LINES", product_preview_data["verts"], decorator_color, product_preview_data["edges"]) - self.draw_batch( - "TRIS", product_preview_data["verts"], transparent_color(decorator_color), product_preview_data["tris"] - ) - def draw_input_ui(self, context): texts = { "D": "Distance: ", @@ -1064,3 +709,390 @@ class PolylineDecorator: self.draw_batch("POINTS", polyline_verts, decorator_color_unselected) if len(polyline_verts) > 1: self.draw_batch("LINES", polyline_verts, decorator_color_unselected, polyline_edges) + + +class ProductDecorator: + is_installed = False + handlers = [] + relating_type = None + + @classmethod + def install(cls, context): + if cls.is_installed: + cls.uninstall() + handler = cls() + cls.handlers.append( + SpaceView3D.draw_handler_add(handler.draw_product_preview, (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 get_wall_preview_data(cls, context, relating_type): + def create_bmesh_from_vertices(vertices): + bm = bmesh.new() + + new_verts = [bm.verts.new(v) for v in polyline_vertices] + if is_closed: + new_edges = [bm.edges.new((new_verts[i], new_verts[i + 1])) for i in range(len(new_verts) - 1)] + new_edges.append( + bm.edges.new((new_verts[-1], new_verts[0])) + ) # Add an edge between the last an first point to make it closed. + else: + new_edges = [bm.edges.new((new_verts[i], new_verts[i + 1])) for i in range(len(new_verts) - 1)] + + bm.verts.index_update() + bm.edges.index_update() + return bm + + # Get properties from object type + layers = tool.Model.get_material_layer_parameters(relating_type) + if not layers["thickness"]: + return + thickness = layers["thickness"] + model_props = context.scene.BIMModelProperties + direction_sense = model_props.direction_sense + direction = 1 + if direction_sense == "NEGATIVE": + direction = -1 + offset_type = model_props.offset_type + offset = 0 + if offset_type == "CENTER": + offset = -thickness / 2 + elif offset_type == "INTERIOR": + offset = -thickness + + unit_system = tool.Drawing.get_unit_system() + factor = 1 + if unit_system == "IMPERIAL": + factor = 3.048 + if unit_system == "METRIC": + unit_length = context.scene.unit_settings.length_unit + if unit_length == "MILLIMETERS": + factor = 1000 + + # For the model properties, the offset value should just be converted + # However, for the wall preview logic that follows, offset and thickness must change direction + model_props.offset = offset * factor + thickness *= direction + offset *= direction + + height = float(model_props.extrusion_depth) + rl = float(model_props.rl1) + x_angle = float(model_props.x_angle) + angle_distortion = height * tan(x_angle) + + wall_preview_data = {} + wall_preview_data["verts"] = [] + + # Verts + polyline_vertices = [] + polyline_data = context.scene.BIMPolylineProperties.insertion_polyline + polyline_points = polyline_data[0].polyline_points if polyline_data else [] + if len(polyline_points) < 2: + wall_preview_data = [] + return + for point in polyline_points: + polyline_vertices.append(Vector((point.x, point.y, point.z))) + + is_closed = False + if ( + polyline_vertices[0].x == polyline_vertices[-1].x + and polyline_vertices[0].y == polyline_vertices[-1].y + and polyline_vertices[0].z == polyline_vertices[-1].z + ): + is_closed = True + polyline_vertices.pop(-1) # Remove the last point. The edges are going to inform that the shape is closed. + + bm_base = create_bmesh_from_vertices(polyline_vertices) + base_vertices = tool.Cad.offset_edges(bm_base, offset) + offset_base_verts = tool.Cad.offset_edges(bm_base, thickness + offset) + top_vertices = tool.Cad.offset_edges(bm_base, angle_distortion + offset) + offset_top_verts = tool.Cad.offset_edges(bm_base, angle_distortion + thickness + offset) + if is_closed: + base_vertices.append(base_vertices[0]) + offset_base_verts.append(offset_base_verts[0]) + top_vertices.append(top_vertices[0]) + offset_top_verts.append(offset_top_verts[0]) + + if offset_base_verts is not None: + for v in base_vertices: + wall_preview_data["verts"].append((v.co.x, v.co.y, v.co.z + rl)) + + for v in offset_base_verts[::-1]: + wall_preview_data["verts"].append((v.co.x, v.co.y, v.co.z + rl)) + + for v in top_vertices: + wall_preview_data["verts"].append((v.co.x, v.co.y, v.co.z + rl + height)) + + for v in offset_top_verts[::-1]: + wall_preview_data["verts"].append((v.co.x, v.co.y, v.co.z + rl + height)) + + bm_base.free() + + # Edges and Tris + points = [] + side_edges_1 = [] + side_edges_2 = [] + base_edges = [] + + for i in range(len(wall_preview_data["verts"])): + points.append(Vector(wall_preview_data["verts"][i])) + + n = len(points) // 2 + bottom_side_1 = [[i, (i + 1) % (n)] for i in range((n - 1) // 2)] + bottom_side_2 = [[i, (i + 1) % (n)] for i in range(n // 2, n - 1)] + bottom_connections = [[i, n - i - 1] for i in range(n // 2)] + bottom_loop = bottom_connections + bottom_side_1 + bottom_side_2 + side_edges_1.extend(bottom_side_1) + side_edges_2.extend(bottom_side_2) + base_edges.extend(bottom_loop) + + upper_side_1 = [[i + n for i in edges] for edges in bottom_side_1] + upper_side_2 = [[i + n for i in edges] for edges in bottom_side_2] + upper_loop = [[i + n for i in edges] for edges in bottom_loop] + side_edges_1.extend(upper_side_1) + side_edges_2.extend(upper_side_2) + base_edges.extend(upper_loop) + + loops = [side_edges_1, side_edges_2, base_edges] + + wall_preview_data["edges"] = [] + wall_preview_data["tris"] = [] + for i, group in enumerate(loops): + bm = bmesh.new() + + new_verts = [bm.verts.new(v) for v in points] + new_edges = [bm.edges.new((new_verts[e[0]], new_verts[e[1]])) for e in group] + + bm.verts.index_update() + bm.edges.index_update() + + if i == 2: + new_faces = bmesh.ops.contextual_create(bm, geom=bm.edges) + new_faces = bmesh.ops.bridge_loops(bm, edges=bm.edges, use_pairs=True, use_cyclic=True) + + bm.verts.index_update() + bm.edges.index_update() + edges = [[v.index for v in e.verts] for e in bm.edges] + tris = [[l.vert.index for l in loop] for loop in bm.calc_loop_triangles()] + wall_preview_data["edges"].extend(edges) + wall_preview_data["tris"].extend(tris) + + wall_preview_data["edges"] = list(set(tuple(e) for e in wall_preview_data["edges"])) + wall_preview_data["tris"] = list(set(tuple(t) for t in wall_preview_data["tris"])) + + return wall_preview_data + + def get_product_preview_data(cls, context, relating_type): + model_props = context.scene.BIMModelProperties + if relating_type.is_a("IfcDoorType"): + rl = float(model_props.rl1) + elif relating_type.is_a("IfcWindowType"): + rl = float(model_props.rl2) + else: + rl = 0 + snap_prop = context.scene.BIMPolylineProperties.snap_mouse_point[0] + mouse_point = Vector((snap_prop.x, snap_prop.y, snap_prop.z)) + snap_obj = bpy.data.objects.get(snap_prop.snap_object) + snap_element = tool.Ifc.get_entity(snap_obj) + rot_mat = Quaternion() + if snap_element and snap_element.is_a("IfcWall"): + rot_mat = snap_obj.matrix_world.to_quaternion() + + obj_type = tool.Ifc.get_object(relating_type) + if obj_type.data: + data = ItemDecorator.get_obj_data(obj_type) + 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 = material.MaterialProfiles[0].Profile + except: + return {} + + model_props = context.scene.BIMModelProperties + extrusion_depth = model_props.extrusion_depth + cardinal_point = model_props.cardinal_point + rot_mat = Quaternion() + if relating_type.is_a("IfcBeamType"): + y_rot = Quaternion((0.0, 1.0, 0.0), radians(90)) + z_rot = Quaternion((0.0, 0.0, 1.0), radians(90)) + rot_mat = y_rot @ z_rot + # Get profile data + settings = ifcopenshell.geom.settings() + settings.set("dimensionality", ifcopenshell.ifcopenshell_wrapper.CURVES_SURFACES_AND_SOLIDS) + shape = ifcopenshell.geom.create_shape(settings, profile) + + 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 extrusion bmesh + bm = bmesh.new() + + new_verts = [bm.verts.new(v) for v in grouped_verts] + new_edges = [bm.edges.new((new_verts[i], new_verts[i + 1])) for i in range(len(grouped_verts) - 1)] + + bm.verts.index_update() + bm.edges.index_update() + + new_faces = bmesh.ops.contextual_create(bm, geom=bm.edges) + + new_faces = bmesh.ops.extrude_face_region(bm, geom=bm.faces) + new_verts = [e for e in new_faces["geom"] if isinstance(e, bmesh.types.BMVert)] + new_faces = bmesh.ops.translate(bm, verts=new_verts, vec=(0.0, 0.0, extrusion_depth)) + + bm.verts.index_update() + bm.edges.index_update() + tris = [[loop.vert.index for loop in triangles] for triangles in bm.calc_loop_triangles()] + + # Create bounding box + verts = bm.verts + i = len(verts) + + min_x = min(v.co.x for v in verts) + max_x = max(v.co.x for v in verts) + min_y = min(v.co.y for v in verts) + max_y = max(v.co.y for v in verts) + min_z = min(v.co.z for v in verts) + max_z = max(v.co.z for v in verts) + + bbox_verts = [ + (min_x, min_y, min_z), + (max_x, min_y, min_z), + (max_x, max_y, min_z), + (min_x, max_y, min_z), + (min_x, min_y, max_z), + (max_x, min_y, max_z), + (max_x, max_y, max_z), + (min_x, max_y, max_z), + ] + + bbox_edges = [ + (0 + i, 3 + i), + (3 + i, 7 + i), + (7 + i, 4 + i), + (4 + i, 0 + i), + (0 + i, 1 + i), + (3 + i, 2 + i), + (7 + i, 6 + i), + (4 + i, 5 + i), + (1 + i, 2 + i), + (2 + i, 6 + i), + (6 + i, 5 + i), + (5 + i, 1 + i), + ] + + # Calculate rotation, mouse position, angle and cardinal point + # TODO Angle + snap_prop = context.scene.BIMPolylineProperties.snap_mouse_point[0] + mouse_point = Vector((snap_prop.x, snap_prop.y, snap_prop.z)) + data = {} + + verts = [tuple(v.co) for v in verts] + verts.extend(bbox_verts) + verts = [tuple(rot_mat @ Vector(v)) for v in verts] + verts = [tuple(Vector(v) + mouse_point) for v in verts] + data["verts"] = verts + data["edges"] = bbox_edges + # data["edges"] = [(edge.verts[0].index, edge.verts[1].index) for edge in bm.edges] + data["tris"] = tris + + bm.free() + return data + + def draw_product_preview(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) + decorator_color = self.addon_prefs.decorator_color_special + polyline_data = context.scene.BIMPolylineProperties.insertion_polyline + polyline_points = polyline_data[0].polyline_points if polyline_data else [] + + self.relating_type = None + props = context.scene.BIMModelProperties + relating_type_id = props.relating_type_id + if relating_type_id: + self.relating_type = tool.Ifc.get().by_id(int(relating_type_id)) + + # Wall + wall_preview_data = self.get_wall_preview_data(context, self.relating_type) + if wall_preview_data: + self.draw_batch("LINES", wall_preview_data["verts"], decorator_color, wall_preview_data["edges"]) + self.draw_batch( + "TRIS", wall_preview_data["verts"], transparent_color(decorator_color), wall_preview_data["tris"] + ) + + # Mesh type products + product_preview_data = self.get_product_preview_data(context, self.relating_type) + if product_preview_data: + self.draw_batch("LINES", product_preview_data["verts"], decorator_color, product_preview_data["edges"]) + self.draw_batch( + "TRIS", product_preview_data["verts"], transparent_color(decorator_color), product_preview_data["tris"] + ) + + # Profile type products + product_preview_data = self.get_profile_preview_data(context, self.relating_type) + if product_preview_data: + self.draw_batch("LINES", product_preview_data["verts"], decorator_color, product_preview_data["edges"]) + self.draw_batch( + "TRIS", product_preview_data["verts"], transparent_color(decorator_color), product_preview_data["tris"] + ) diff --git a/src/bonsai/bonsai/bim/module/model/product.py b/src/bonsai/bonsai/bim/module/model/product.py index cf55cb8378..b9646bce63 100644 --- a/src/bonsai/bonsai/bim/module/model/product.py +++ b/src/bonsai/bonsai/bim/module/model/product.py @@ -39,7 +39,7 @@ from bonsai.bim.ifc import IfcStore from bonsai.bim.helper import get_enum_items from bonsai.bim.module.model.data import AuthoringData from bonsai.bim.module.model.polyline import PolylineOperator -from bonsai.bim.module.model.decorator import PolylineDecorator +from bonsai.bim.module.model.decorator import PolylineDecorator, ProductDecorator from mathutils import Vector, Matrix from bpy_extras.object_utils import AddObjectHelper import json @@ -187,12 +187,14 @@ class AddOccurrence(bpy.types.Operator, PolylineOperator): cancel = self.handle_cancelation(context, event) if cancel is not None: + ProductDecorator.uninstall() return cancel return {"RUNNING_MODAL"} def invoke(self, context, event): super().invoke(context, event) + ProductDecorator.install(context) self.tool_state.use_default_container = True self.tool_state.plane_method = "XY" return {"RUNNING_MODAL"} diff --git a/src/bonsai/bonsai/bim/module/model/wall.py b/src/bonsai/bonsai/bim/module/model/wall.py index 8663249fd0..cb4fb47454 100644 --- a/src/bonsai/bonsai/bim/module/model/wall.py +++ b/src/bonsai/bonsai/bim/module/model/wall.py @@ -37,7 +37,7 @@ from bonsai.bim.ifc import IfcStore from math import pi, sin, cos, degrees from mathutils import Vector, Matrix from bonsai.bim.module.model.opening import FilledOpeningGenerator -from bonsai.bim.module.model.decorator import PolylineDecorator +from bonsai.bim.module.model.decorator import PolylineDecorator, ProductDecorator from bonsai.bim.module.model.polyline import PolylineOperator from typing import Optional from lark import Lark, Transformer @@ -383,6 +383,7 @@ class DrawPolylineWall(bpy.types.Operator, PolylineOperator): ): self.create_walls_from_polyline(context) context.workspace.status_text_set(text=None) + ProductDecorator.uninstall() PolylineDecorator.uninstall() tool.Polyline.clear_polyline() tool.Blender.update_viewport() @@ -394,12 +395,14 @@ class DrawPolylineWall(bpy.types.Operator, PolylineOperator): cancel = self.handle_cancelation(context, event) if cancel is not None: + ProductDecorator.uninstall() return cancel return {"RUNNING_MODAL"} def invoke(self, context, event): super().invoke(context, event) + ProductDecorator.install(context) self.tool_state.use_default_container = True self.tool_state.plane_method = "XY" return {"RUNNING_MODAL"}