From f6307520e96dc62f217d7798be3bfe8edb67e0b6 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Wed, 19 Jul 2023 16:53:39 +0500 Subject: [PATCH] Support looped railings #3415 Example - https://imgur.com/a/RaLORbk --- .../blenderbim/bim/module/model/railing.py | 26 +++++++--- .../geometry/add_railing_representation.py | 50 +++++++++++++------ 2 files changed, 54 insertions(+), 22 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/model/railing.py b/src/blenderbim/blenderbim/bim/module/model/railing.py index f70c1c57e0..d6b1246e5d 100644 --- a/src/blenderbim/blenderbim/bim/module/model/railing.py +++ b/src/blenderbim/blenderbim/bim/module/model/railing.py @@ -83,6 +83,7 @@ def update_railing_modifier_ifc_data(context): pset_data = tool.Model.get_modeling_bbim_pset_data(bpy.context.active_object, "BBIM_Railing") path_data = pset_data["data_dict"]["path_data"] railing_path = [Vector(v) for v in path_data["verts"]] + looped_path = path_data["edges"][-1][-1] == path_data["edges"][0][0] si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) representation_data = { @@ -95,6 +96,7 @@ def update_railing_modifier_ifc_data(context): "clear_width": props.clear_width / si_conversion, "terminal_type": props.terminal_type, "height": props.height / si_conversion, + "looped_path": looped_path, } model_representation = ifcopenshell.api.run( "geometry.add_railing_representation", ifc_file, **representation_data @@ -213,10 +215,14 @@ def get_path_data(obj): si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) bm = tool.Blender.get_bmesh_for_mesh(obj.data) - end_points = [v for v in bm.verts if len(v.link_edges) == 1] - if not end_points: - return None + if not bm.verts or not bm.edges: + return + + end_points = [v for v in bm.verts if len(v.link_edges) == 1] + looped = not end_points + + # TODO: check with previous data # if we have some previous data then we try to match # start or end of the path with the previous path previous_data = False @@ -231,11 +237,13 @@ def get_path_data(obj): start_point = potential_start[0] else: start_point = next(v for v in end_points if v != potential_start[0]) - else: + elif not looped: start_point = min(end_points, key=lambda v: v.index) + elif looped: + start_point = bm.verts[:][0] # walking through the path - # to make sure all verts and in consequent order + # to make sure all verts are in consequent order edge = start_point.link_edges[0] v = edge.other_vert(start_point) points = [start_point.co, v.co] @@ -246,10 +254,14 @@ def get_path_data(obj): while len(link_edges := v.link_edges) != 1: prev_v = v - link_edges = v.link_edges + edge = other_edge(link_edges, edge) v = edge.other_vert(prev_v) + if looped and v == start_point: + segments.append((i - 1, 0)) + break + # skip path verts if they just go vertical to avoid errors if (v.co.xy - prev_v.co.xy).length <= 0.0001: continue @@ -318,6 +330,8 @@ class AddRailing(bpy.types.Operator, tool.Ifc.Operator): railing_data = props.get_general_kwargs(convert_to_project_units=True) path_data = get_path_data(obj) + + # NOTE: will occur only on meshes without edges or verts if not path_data: path_data = { "edges": [[0, 1], [1, 2]], diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_railing_representation.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_railing_representation.py index 2caca39dbd..f25ea9eb4f 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_railing_representation.py +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_railing_representation.py @@ -54,6 +54,7 @@ class Usecase: "clear_width": self.convert_si_to_unit(mm(40)), "terminal_type": "180", "height": self.convert_si_to_unit(mm(1000)), + "looped_path": False, } ) @@ -80,6 +81,7 @@ class Usecase: cap_type = self.settings["terminal_type"] ifc_context = self.settings["context"] railing_coords = self.settings["railing_path"] + looped_path = self.settings["looped_path"] railing_coords = [p - z_down * railing_radius for p in railing_coords] # constant @@ -157,7 +159,10 @@ class Usecase: prev_dir = cur_dir i = i + 1 - output_points.append(base_points[-1]) + if looped_path: + output_points[0] = output_points[-1] + else: + output_points.append(base_points[-1]) return output_points def create_supports_items(railing_coords, manual_supports=False): @@ -215,37 +220,45 @@ class Usecase: ortho_dir = -ortho_dir arc_middle_point_cos = sin(radians(45)) - - if cap_type in ('180', 'TO_END_POST'): + + if cap_type in ("180", "TO_END_POST"): arc_point = start_point + cap_dir * terminal_radius + terminal_radius * z_down arc_points.append(arc_point) cap_coords = [arc_point, start_point + terminal_radius * 2 * z_down] - if cap_type == 'TO_END_POST': + if cap_type == "TO_END_POST": end_point = railing_coords_for_cap[-2].copy() end_point.z -= terminal_radius * 2 cap_coords.append(end_point) - - elif cap_type == 'TO_WALL': - arc_point = start_point + cap_dir * clear_width * arc_middle_point_cos + ortho_dir * clear_width * (1-arc_middle_point_cos) + + elif cap_type == "TO_WALL": + arc_point = ( + start_point + + cap_dir * clear_width * arc_middle_point_cos + + ortho_dir * clear_width * (1 - arc_middle_point_cos) + ) arc_points.append(arc_point) cap_coords = [arc_point, start_point + ortho_dir * clear_width + cap_dir * clear_width] - elif cap_type == 'TO_FLOOR': - arc_point = start_point + cap_dir * terminal_radius * arc_middle_point_cos + z_down * terminal_radius * (1-arc_middle_point_cos) + elif cap_type == "TO_FLOOR": + arc_point = ( + start_point + + cap_dir * terminal_radius * arc_middle_point_cos + + z_down * terminal_radius * (1 - arc_middle_point_cos) + ) arc_points.append(arc_point) arc_end = start_point + cap_dir * terminal_radius + terminal_radius * z_down cap_coords = [ - arc_point, + arc_point, arc_end, - arc_end+z_down*(height-terminal_radius), + arc_end + z_down * (height - terminal_radius), ] - elif cap_type == 'TO_END_POST_AND_FLOOR': + elif cap_type == "TO_END_POST_AND_FLOOR": first_arc_end = start_point + cap_dir * terminal_radius + terminal_radius * z_down first_arc_coords = get_fillet_points( - start_point, start_point + cap_dir * terminal_radius, - first_arc_end, terminal_radius) + start_point, start_point + cap_dir * terminal_radius, first_arc_end, terminal_radius + ) arc_points.append(first_arc_coords[1]) end_point = railing_coords_for_cap[-2].copy() @@ -255,17 +268,22 @@ class Usecase: ) arc_points.append(second_arc_coords[1]) cap_coords = [start_point] + first_arc_coords + second_arc_coords + [end_point] - + railing_coords = railing_coords_for_cap + cap_coords if start: railing_coords = railing_coords[::-1] return railing_coords, arc_points + # need to add first two points to the path + # to create the turning arcs and supports on the last segment of the loop + if looped_path: + railing_coords += railing_coords[:2] + items_3d.extend(create_supports_items(railing_coords, manual_supports=use_manual_supports)) railing_coords = add_arcs_on_turnings_points(railing_coords) - if cap_type != 'NONE': + if not looped_path and cap_type != "NONE": railing_coords, arc_points = add_cap(railing_coords, arc_points, start=True) railing_coords, arc_points = add_cap(railing_coords, arc_points, start=False)