diff --git a/src/blenderbim/blenderbim/bim/module/model/mep.py b/src/blenderbim/blenderbim/bim/module/model/mep.py index 54e62e54cc..75c74aa89d 100644 --- a/src/blenderbim/blenderbim/bim/module/model/mep.py +++ b/src/blenderbim/blenderbim/bim/module/model/mep.py @@ -882,12 +882,6 @@ class MEPAddBend(bpy.types.Operator, tool.Ifc.Operator): ifc_file = tool.Ifc.get() si_conversion = ifcopenshell.util.unit.calculate_unit_scale(ifc_file) - self.start_length, self.end_length = 0, 0 - - if not (self.start_length == 0 and self.end_length == 0): - self.report({"ERROR"}, f"Only zero lengths are now supported.") - return {"CANCELLED"} - if self.start_segment_id and self.end_segment_id: start_element = ifc_file.by_id(self.start_segment_id) end_element = ifc_file.by_id(self.end_segment_id) @@ -971,7 +965,7 @@ class MEPAddBend(bpy.types.Operator, tool.Ifc.Operator): end_segment_data["end_point"]: end_segment_data["end_port"], } - get_z_basis = lambda o: o.matrix_world.col[2].normalized().to_3d() + get_z_basis = lambda o: tool.Cad.get_basis_vector(o, 2) segments_intersection_ws = tool.Cad.intersect_edges( (start_object.location, start_object.location + get_z_basis(start_object)), (end_object.location, end_object.location + get_z_basis(end_object)), @@ -1058,8 +1052,8 @@ class MEPAddBend(bpy.types.Operator, tool.Ifc.Operator): current_start_offset = segments_intersection.length current_end_offset = (segments_intersection - profile_offset).length - start_extend = current_start_offset - required_offset - end_extend = current_end_offset - required_offset + start_extend = current_start_offset - (required_offset + self.start_length) + end_extend = current_end_offset - (required_offset + self.end_length) return start_extend, end_extend diff --git a/src/blenderbim/blenderbim/tool/cad.py b/src/blenderbim/blenderbim/tool/cad.py index aca5ee6e06..41a66db0c2 100644 --- a/src/blenderbim/blenderbim/tool/cad.py +++ b/src/blenderbim/blenderbim/tool/cad.py @@ -526,3 +526,7 @@ class Cad: if cls.is_x(value, 0): return 0 return 1 if value > 0 else -1 + + @classmethod + def get_basis_vector(cls, object, axis_i): + return object.matrix_world.col[axis_i].normalized().to_3d() diff --git a/src/ifcopenshell-python/ifcopenshell/util/shape_builder.py b/src/ifcopenshell-python/ifcopenshell/util/shape_builder.py index f825b92db4..c94581b71c 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/shape_builder.py +++ b/src/ifcopenshell-python/ifcopenshell/util/shape_builder.py @@ -1356,10 +1356,26 @@ class ShapeBuilder: O[lateral_axis] = (radius + profile_dim[lateral_axis]) * lateral_sign theta = angle + def get_circle_point(angle, radius): + point = V(0, 0, 0) + angle -= pi / 2 + # fmt: off + point.z = z_sign * cos(angle) * radius + point[lateral_axis] = lateral_sign * sin(angle) * radius + # fmt: on + return point + + def get_circle_tangent(angle): + tangent = V(0, 0, 0) + tangent.z = cos(angle) * z_sign + tangent[lateral_axis] = sin(angle) * lateral_sign + return tangent + def get_bend_representation_item(): if is_circular_profile: theta_segments = [0, theta / 2, theta] else: + # TODO: try to replace with curves instead of segments # get as much segment_length segments as possible segment_length = pi / 20 num_segments = ceil(theta / segment_length) @@ -1371,32 +1387,27 @@ class ShapeBuilder: r = radius if is_circular_profile: r += profile_dim[lateral_axis] + else: + outer_r = r + 2 * profile_dim[lateral_axis] for cur_theta in theta_segments: - cur_theta -= pi / 2 - inner = V(0, 0, 0) - # fmt: off - inner.z = z_sign * cos(cur_theta) * r - inner[lateral_axis] = lateral_sign * sin(cur_theta) * r - inner_points.append(inner) - + inner_points.append(get_circle_point(cur_theta, r)) if not is_circular_profile: - outer = V(0, 0, 0) - outer.z = z_sign * cos(cur_theta) * (r + 2 * profile_dim[lateral_axis]) - outer[lateral_axis] = lateral_sign * sin(cur_theta) * (r + 2 * profile_dim[lateral_axis]) - outer_points.append(outer) - # fmt: on + outer_points.append(get_circle_point(cur_theta, outer_r)) points = inner_points + outer_points[::-1] points = [p + O for p in points] + offset = V(0, 0, 0) + offset.z = z_sign * start_length if is_circular_profile: - bend_path = self.polyline(points, closed=False, arc_points=[1]) + bend_path = self.polyline(points, closed=False, arc_points=[1], position_offset=offset) bend = self.create_swept_disk_solid(bend_path, profile_dim[lateral_axis]) else: main_axes = lambda v: getattr(v, "xy"[lateral_axis] + "z") - offset = V(0, 0, 0) + offset[non_lateral_axis] = -profile_dim[non_lateral_axis] + extrusion_kwargs = self.extrude_kwargs("XY"[non_lateral_axis]) profile_curve = self.polyline([main_axes(p) for p in points], closed=True) bend = self.extrude( @@ -1405,6 +1416,28 @@ class ShapeBuilder: return bend rep_items.append(get_bend_representation_item()) + if start_length: + rep_items.append(self.extrude(profile, start_length, extrusion_vector=V(0, 0, z_sign))) + if end_length: + end_position = O + get_circle_point(theta, radius + profile_dim[lateral_axis]) + end_position.z += start_length * z_sign + + # define extrusion space for the segment after the bend + z_axis = get_circle_tangent(theta) + extrude_kwargs = { + "position_z_axis": z_axis, + "extrusion_vector": Vector((0, 0, 1)), + } + # since we are sure that tangent involves only two axis + # it's safe to assume that non lateral axis is untouched + if lateral_axis == 0: + x_axis = z_axis.cross(Vector((0, 1, 0))) + else: + x_axis = Vector((1, 0, 0)) + extrude_kwargs["position_x_axis"] = x_axis + + rep_items.append(self.extrude(profile, end_length, end_position, **extrude_kwargs)) + body = ifcopenshell.util.representation.get_context(self.file, "Model", "Body", "MODEL_VIEW") rep = self.get_representation(body, rep_items)