From 8d18a8a0232a76bd444dc72431d5fc2dbb4d25fc Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Wed, 28 Jun 2023 15:48:21 +0500 Subject: [PATCH] Fix for f88835b #3271 Now we do not rely on small offset to keep using inner curves to create linings, if some part of the lining is not present (which happens on mullions and transoms) we'll create U or L shape extrusion instead of rectangle with inner curve. Long story short - this should keep ifc for windows valid. --- .../blenderbim/bim/module/model/window.py | 26 +-- .../api/geometry/add_window_representation.py | 155 ++++++++++++------ 2 files changed, 123 insertions(+), 58 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/model/window.py b/src/blenderbim/blenderbim/bim/module/model/window.py index 226834c8a5..fea2fad5d5 100644 --- a/src/blenderbim/blenderbim/bim/module/model/window.py +++ b/src/blenderbim/blenderbim/bim/module/model/window.py @@ -262,7 +262,7 @@ def create_bm_window( frame_thickness, glass_thickness, position: Vector, - x_offsets: list = None + x_offsets: list = None, ): """`lining_thickness` and `x_offsets` are expected to be defined as a list, similarly to `create_bm_window_frame` `thickness` argument""" @@ -334,7 +334,7 @@ def update_window_modifier_bmesh(context): # detect transom has_transom = unique_rows_in_col[column_i] > 1 first_row = row_i == 0 - last_row = row_i == unique_rows_in_col[column_i] - 1 + last_row = row_i == unique_rows_in_col[column_i] - 1 top_to_transom = has_transom and not first_row bottom_to_transom = has_transom and not last_row @@ -366,6 +366,7 @@ def update_window_modifier_bmesh(context): frame_depth = props.frame_depth[panel_i] frame_thickness = props.frame_thickness[panel_i] + lining_to_panel_offset_y_full = (lining_depth - frame_depth) + lining_to_panel_offset_y # add window window_lining_size = V( panel_width, @@ -375,6 +376,7 @@ def update_window_modifier_bmesh(context): # calculate lining thickness and frame size / offset # taking into account mullions and transoms + # fmt: off window_lining_thickness = [ mullion_thickness if right_to_mullion else lining_thickness, transom_thickness if bottom_to_transom else lining_thickness, @@ -382,23 +384,23 @@ def update_window_modifier_bmesh(context): transom_thickness if top_to_transom else lining_thickness, ] - lining_to_panel_offset_y_full = (lining_depth - frame_depth) + lining_to_panel_offset_y - - # x offsets can differ if there are mullions or transoms because we're trying to maintain symmetry + # x offsets can differ if there are mullions or transoms because we're trying to maintain symmetry base_frame_clear = lining_to_panel_offset_x + frame_thickness - lining_thickness current_offset_x = base_frame_clear - frame_thickness + mullion_thickness current_offset_z = base_frame_clear - frame_thickness + transom_thickness + # fmt: off x_offsets = [ - current_offset_x if right_to_mullion else lining_to_panel_offset_x, # LEFT - current_offset_z if bottom_to_transom else lining_to_panel_offset_x, # TOP - current_offset_x if left_to_mullion else lining_to_panel_offset_x, # RIGHT - current_offset_z if top_to_transom else lining_to_panel_offset_x, # BOTTOM + current_offset_x if right_to_mullion else lining_to_panel_offset_x, # LEFT + current_offset_z if bottom_to_transom else lining_to_panel_offset_x, # TOP + current_offset_x if left_to_mullion else lining_to_panel_offset_x, # RIGHT + current_offset_z if top_to_transom else lining_to_panel_offset_x, # BOTTOM ] + # fmt: on frame_size = window_lining_size.copy() frame_size.y = frame_depth - frame_size.x -= (x_offsets[0] + x_offsets[2]) - frame_size.z -= (x_offsets[1] + x_offsets[3]) + frame_size.x -= x_offsets[0] + x_offsets[2] + frame_size.z -= x_offsets[1] + x_offsets[3] window_position = V(accumulated_width, 0, accumulated_height[column_i]) lining_verts, panel_verts, glass_verts = create_bm_window( @@ -411,7 +413,7 @@ def update_window_modifier_bmesh(context): frame_thickness, glass_thickness, window_position, - x_offsets + x_offsets, ) built_panels.append(panel_i) diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_window_representation.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_window_representation.py index 7ec74ae4b4..47b2f3745b 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_window_representation.py +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_window_representation.py @@ -42,7 +42,9 @@ DEFAULT_PANEL_SCHEMAS = { } -def create_ifc_window_frame_simple(builder, size: Vector, thickness: list, position: Vector = V(0, 0, 0).freeze()): +def create_ifc_window_frame_simple( + builder: ShapeBuilder, size: Vector, thickness: list, position: Vector = V(0, 0, 0).freeze() +): """`thickness` of the profile is defined as list in the following order: `(LEFT, TOP, RIGHT, BOTTOM)` @@ -51,24 +53,91 @@ def create_ifc_window_frame_simple(builder, size: Vector, thickness: list, posit if not isinstance(thickness, collections.abc.Iterable): thickness = [thickness] * 4 - th_left, th_up, th_right, th_bottom = thickness - panel_rect = builder.rectangle(size=size.xz) + def get_extruded_profile(profile): + return builder.extrude( + profile, + size.y, + position_x_axis=V(1, 0, 0), + position_z_axis=V(0, -1, 0), + extrusion_vector=V(0, 0, -1), + position=position, + ) - inner_rect_size = size - V(th_left + th_right, 0, th_bottom + th_up) - inner_rect = builder.rectangle(size=inner_rect_size.xz, position=V(th_left, th_bottom)) + # if all lining sides are present then we can just use two rectangles + # as inner and outer curves of the profile + if thickness.count(0) == 0: + panel_rect = builder.rectangle(size=size.xz) - panel_profile = builder.profile(panel_rect, inner_curves=inner_rect) - panel_extruded = builder.extrude( - panel_profile, - size.y, - position_x_axis=V(1, 0, 0), - position_z_axis=V(0, -1, 0), - extrusion_vector=V(0, 0, -1), - position=position, - ) - return panel_extruded + inner_rect_size = size - V(th_left + th_right, 0, th_bottom + th_up) + inner_rect = builder.rectangle(size=inner_rect_size.xz, position=V(th_left, th_bottom)) + + panel_profile = builder.profile(panel_rect, inner_curves=inner_rect) + return [get_extruded_profile(panel_profile)] + + # if some side has zero thickness it means we cannot use inner curves + # and need to generate L/U shape or just separate rectangles + else: + + def get_segments_from_thickness(): + nonlocal thickness + segments = [] + cur_segment = [] + for i, thickness in enumerate(thickness): + if thickness == 0: + if cur_segment: + segments.append(tuple(cur_segment)) + cur_segment = [] + else: + cur_segment.append(i) + + if cur_segment: + if len(segments) > 0 and segments[0][0] == 0: + segments[0] = tuple(cur_segment) + segments[0] + else: + segments.append(tuple(cur_segment)) + return segments + + # prepare coords to build a lining + # fmt: off + outer_coords = [ + (V(0, 0), V(0, size.z)), + (V(0, size.z), V(size.x, size.z)), + (V(size.x, size.z), V(size.x, 0)), + (V(size.x, 0), V(0, 0)), + ] + inner_coords = [ + (V(th_left, th_bottom), V(th_left, size.z - th_up)), + (V(th_left, size.z - th_up), V(size.x - th_right, size.z - th_up)), + (V(size.x - th_right, size.z - th_up), V(size.x - th_right, th_bottom)), + (V(size.x - th_right, th_bottom), V(th_left, th_bottom)), + ] + # fmt: on + + def get_points(segment): + points = [] + for side in segment: + outer = outer_coords[side] + if side == segment[0]: # first segment + points.append(outer[0]) + points.append(outer[1]) + + for side in reversed(segment): + inner = inner_coords[side] + if side == segment[-1]: # last non zero segment + points.append(inner[1]) + points.append(inner[0]) + return points + + segments = get_segments_from_thickness() + segments_items = [] + for seg in segments: + polyline = builder.polyline(points=get_points(seg), closed=True) + panel_profile = builder.profile(polyline) + segments_items.append(get_extruded_profile(panel_profile)) + + return segments_items def window_l_shape_check( @@ -95,7 +164,6 @@ def create_ifc_window( frame_thickness, glass_thickness, position: Vector, - unit_scale, # different from bmesh `create_bm_window` x_offsets: list = None, ): """`lining_thickness` and `x_offsets` are expected to be defined as a list, @@ -121,20 +189,15 @@ def create_ifc_window( second_lining_size = lining_size.copy() second_lining_size.y = lining_size.y - lining_to_panel_offset_y_full second_lining_position = V(0, lining_to_panel_offset_y_full, 0) + second_lining_thickness = [min(th, x_offset) for th, x_offset in zip(lining_thickness, x_offsets, strict=True)] - # we're using some safe thickness so thickness won't end = 0 - # resulting in errors `create_ifc_window_frame_simple` - # because it's using inner curves to create lining - safe_thickness = 0.00001 / unit_scale - second_lining_thickness = [max(min(th, x_offset), safe_thickness) for th, x_offset in zip(lining_thickness, x_offsets, strict=True)] - - second_lining = create_ifc_window_frame_simple( + second_lining_items = create_ifc_window_frame_simple( builder, second_lining_size, second_lining_thickness, second_lining_position ) - lining_items.append(second_lining) + lining_items.extend(second_lining_items) - main_lining = create_ifc_window_frame_simple(builder, main_lining_size, lining_thickness) - lining_items.append(main_lining) + main_lining_items = create_ifc_window_frame_simple(builder, main_lining_size, lining_thickness) + lining_items.extend(main_lining_items) frame_position = V( x_offsets[0], @@ -142,10 +205,10 @@ def create_ifc_window( x_offsets[3], ) - frame_extruded = create_ifc_window_frame_simple(builder, frame_size, frame_thickness, frame_position) + frame_extruded_items = create_ifc_window_frame_simple(builder, frame_size, frame_thickness, frame_position) glass_position = frame_position + V(0, frame_size.y / 2 - glass_thickness / 2, 0) - glass_rect = builder.deep_copy(frame_extruded.SweptArea.InnerCurves[0]) + glass_rect = builder.deep_copy(frame_extruded_items[0].SweptArea.InnerCurves[0]) glass = builder.extrude( glass_rect, glass_thickness, @@ -155,7 +218,7 @@ def create_ifc_window( position=glass_position, ) - output_items = [lining_items, [frame_extruded], [glass]] + output_items = [lining_items, frame_extruded_items, [glass]] builder.translate(chain(*output_items), position) return output_items @@ -369,8 +432,8 @@ class Usecase: cur_panel_items.extend( [ get_lining_shape( - window_lining_thickness[0], - closed=closed_lining[0], + window_lining_thickness[0], + closed=closed_lining[0], x_offset=current_offset_x if right_to_mullion else None, ), get_lining_shape( @@ -386,13 +449,13 @@ class Usecase: frame_items = [] frame_position = V( - current_offset_x if right_to_mullion else lining_to_panel_offset_x, - lining_to_panel_offset_y_full, + current_offset_x if right_to_mullion else lining_to_panel_offset_x, + lining_to_panel_offset_y_full, ) frame_width = panel_width - frame_width -= current_offset_x if left_to_mullion else lining_to_panel_offset_x - frame_width -= current_offset_x if right_to_mullion else lining_to_panel_offset_x + frame_width -= current_offset_x if left_to_mullion else lining_to_panel_offset_x + frame_width -= current_offset_x if right_to_mullion else lining_to_panel_offset_x frame_vertical = builder.rectangle(size=V(frame_thickness, frame_depth)) frame_items.extend( @@ -443,7 +506,6 @@ class Usecase: unique_cols = len(set(panel_row)) for column_i, panel_i in enumerate(panel_row): - # detect mullion has_mullion = unique_cols > 1 first_column = column_i == 0 @@ -454,7 +516,7 @@ class Usecase: # detect transom has_transom = unique_rows_in_col[column_i] > 1 first_row = row_i == 0 - last_row = row_i == unique_rows_in_col[column_i] - 1 + last_row = row_i == unique_rows_in_col[column_i] - 1 top_to_transom = has_transom and not first_row bottom_to_transom = has_transom and not last_row @@ -490,6 +552,7 @@ class Usecase: frame_thickness = cur_panel["FrameThickness"] lining_to_panel_offset_y_full = (lining_depth - frame_depth) + lining_to_panel_offset_y + # fmt: off # calculate lining thickness and frame size / offset # taking into account mullions and transoms window_lining_thickness = [ @@ -499,22 +562,23 @@ class Usecase: transom_thickness if top_to_transom else lining_thickness, ] - # x offsets can differ if there are mullions or transoms because we're trying to maintain symmetry + # x offsets can differ if there are mullions or transoms because we're trying to maintain symmetry base_frame_clear = lining_to_panel_offset_x + frame_thickness - lining_thickness current_offset_x = base_frame_clear - frame_thickness + mullion_thickness current_offset_z = base_frame_clear - frame_thickness + transom_thickness x_offsets = [ - current_offset_x if right_to_mullion else lining_to_panel_offset_x, # LEFT - current_offset_z if bottom_to_transom else lining_to_panel_offset_x, # TOP - current_offset_x if left_to_mullion else lining_to_panel_offset_x, # RIGHT - current_offset_z if top_to_transom else lining_to_panel_offset_x, # BOTTOM + current_offset_x if right_to_mullion else lining_to_panel_offset_x, # LEFT + current_offset_z if bottom_to_transom else lining_to_panel_offset_x, # TOP + current_offset_x if left_to_mullion else lining_to_panel_offset_x, # RIGHT + current_offset_z if top_to_transom else lining_to_panel_offset_x, # BOTTOM ] + # fmt: on window_lining_size = V(panel_width, lining_depth, panel_height) frame_size = window_lining_size.copy() frame_size.y = frame_depth - frame_size.x -= (x_offsets[0] + x_offsets[2]) - frame_size.z -= (x_offsets[1] + x_offsets[3]) + frame_size.x -= x_offsets[0] + x_offsets[2] + frame_size.z -= x_offsets[1] + x_offsets[3] window_panel_position = V(accumulated_width, 0, accumulated_height[column_i]) # create window panel @@ -528,8 +592,7 @@ class Usecase: frame_thickness, glass_thickness, window_panel_position, - self.settings["unit_scale"], - x_offsets + x_offsets, ) built_panels.append(panel_i) window_items.extend(chain(*current_window_items))