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))