def bogie_pivot_up_y_equation(arm_length, bogie_pivot_up_y): up_angle = get_arm_angle(arm_length, bogie_pivot_up_y) down_angle = up_angle - travel_angle travel = get_arm_travel(arm_length, down_angle, bogie_pivot_up_y) neutral_angle = get_arm_angle( arm_length, bogie_pivot_up_y - (1 - suspension_sag) * travel) bogie_pivot_up_point = Vector(math.cos(up_angle), math.sin(up_angle)) * arm_length left_angle = (up_angle - neutral_angle) - bogie_swing_angle left_wheel_position = bogie_pivot_up_point + get_bogie_wheel_position( left_angle, -1) dist_left = util.point_to_line_distance(left_wheel_position, spring_up_point, spring_anchor_point) dist_right = abs(bogie_pivot_up_point - (spring_down_point + Vector(suspension_spacing, 0))) ret1 = dist_left - wheel_diameter / 2 - vitamins.spring.diameter / 2 - wheel_clearance #ret2 = dist_right - math.hypot(bogie_wheel_spacing / 2, bogie_pivot_z) - wheel_diameter / 2 - arm_thickness / 2 - wheel_clearance return ret1
def arm_generator(thickness, pivot_thickness, width, spring_point_width, arm_length, spring_arm_length, spring_angle_offset, cone_height, wheel_diameter, wheel_clearance, pivot_hole_diameter, wheel_bearing, wheel_screw, spring_screw): cone_upper_diameter = wheel_bearing.id + 2 * wheel_bearing.shoulder_size spring_point = Vector.polar(spring_arm_length, spring_angle_offset) p1, _ = util.outer_tangent(Vector(0, 0), (pivot_thickness - thickness) / 2, Vector(arm_length, 0), 0) _, p2 = util.outer_tangent(spring_point, 0, Vector(0, 0), (pivot_thickness - thickness) / 2) outline = polygon2d([(p1.x, p1.y), (arm_length, 0), (spring_point.x, spring_point.y), (p2.x, p2.y)]) \ .offset(thickness / 2) outline += circle(d=pivot_thickness) arm = outline \ .extruded(spring_point_width, symmetrical=False) arm -= rectangle(wheel_diameter, 2 * spring_point_width) \ .translated_y(spring_point_width + arm_width + wheel_clearance) \ .offset(wheel_clearance) \ .revolved() \ .rotated_x(90) \ .translated_x(arm_length) cone = tools.cone(height=cone_height, upper_diameter=cone_upper_diameter, lower_diameter=(cone_upper_diameter + thickness) / 2 + cone_height, base_height=width) \ .translated_x(arm_length) arm += cone & outline.extruded(float("inf")) arm -= cylinder(d=pivot_hole_diameter, h=float("inf")) arm -= tools.screw_hole_with_nut_pocket(wheel_screw) \ .translated_x(arm_length) arm -= tools.screw_hole_with_nut_pocket(spring_screw) \ .translated(*spring_point) return arm
def spring_arm_length_equation(spring_arm_length): """ Equation describing spring location relative to the pivot. """ spring_anchor_point = get_spring_anchor_point(spring_arm_length) travel_angle = get_travel_angle(spring_arm_length, spring_anchor_point) spring_down_point = get_spring_point(spring_arm_length, travel_angle) spring_axis_to_pivot_point = util.point_to_line_distance( Vector(0, 0), spring_anchor_point, spring_down_point) return spring_axis_to_pivot_point - ( arm_thickness / 2 + vitamins.spring.diameter / 2 + arm_clearance)
def get_wheel_force(arm_length, up_angle, angle): """ Return residual force on a group of wheels. """ spring_point = get_spring_point(spring_arm_length, up_angle - angle) length = abs(spring_point - spring_anchor_point.flattened()) spring_force = vitamins.spring.force(length) torque = spring_force * util.point_to_line_distance( Vector(0, 0), spring_anchor_point.flattened(), spring_point) wheel_force = torque / (arm_length * math.cos(angle)) return wheel_force - parameters.design_weight / bogie_count
def outer_tangent(p1, r1, p2, r2): """ Return a pair of points on circles (p1, r1) and (p2, r2) that form a line tangent to both of these circles. This is always an outer tangent and which one of the two outer tangents is returned is determined by the order of the parameters (it's the one to the left from line segment p1, p2). """ dp = p2 - p1 dr = r2 - r1 dp2 = dp.abs_squared() tmp = Vector(math.sqrt(dp2 - dr * dr), dr) direction = Vector(perpendicular(dp).dot(tmp), -dp.dot(tmp)) / dp2 ret1 = p1 + direction * r1 ret2 = p2 + direction * r2 dret = ret2 - ret1 assert abs(ret1 - p1) == Approx(r1) assert abs(ret2 - p2) == Approx(r2) assert dret.dot(ret1 - p1) == Approx(0) assert dret.dot(ret2 - p2) == Approx(0) return (ret1, ret2)
def tensioner_generator(right, arm_angle=(arm_angle_back + arm_angle_front) / 2): spring_point = Vector.polar( spring_arm_length, arm_angle + spring_angle_offset) + pivot_position v = spring_point.flattened() - spring_anchor_point.flattened() length = abs(v) spring_degrees = 90 - math.degrees(math.atan2(v.y, v.x)) spring = vitamins.spring(length) if right: arm = arm_right multiplier = 1 else: arm = arm_left.rotated_y(180) multiplier = -1 arm_angle = -arm_angle asm = codecad.assembly("tensioner_assembly_" + ("right" if right else "left"), [arm \ .rotated_x(-90) \ .rotated_y(180) \ .rotated_y(arm_angle) \ .translated(pivot_position.x, pivot_position.z, pivot_position.y), wheel_assembly \ .translated_y(-arm_width - wheel_clearance) \ .rotated_z(90 + 90 * multiplier) \ .translated_x(arm_length) \ .rotated_y(arm_angle) \ .translated(pivot_position.x, pivot_position.z, pivot_position.y), spring \ .rotated_y(spring_degrees) \ .translated(spring_anchor_point.x, multiplier * (spring_anchor_point.z + vitamins.spring.top_mount_thickness / 2), spring_anchor_point.y), wheel_screw, wheel_screw.lock_nut, spring_screw, spring_screw.lock_nut, vitamins.shoulder_screw, vitamins.shoulder_screw.lock_nut, vitamins.m5x20_screw, vitamins.m5x20_screw.lock_nut]) return asm
inner_bearing_height) / 2 # Max length so that the pivot is still completely hidden by the wheel arm_pivot_thickness = 2 * (wheel_diameter / 2 - arm_length - wheel_clearance) def spring_arm_length_equation(spring_arm_length): return arm_length**2 + spring_arm_length**2 - 2 * arm_length * spring_arm_length * math.cos(math.radians(spring_angle_offset)) - \ (wheel_diameter / 2 + wheel_clearance + vitamins.spring.bottom_mount_od / 2)**2 spring_arm_length = scipy.optimize.brentq(spring_arm_length_equation, 0, 2 * wheel_diameter) pivot_to_spring_anchor = spring_arm_length + vitamins.spring.length - vitamins.spring.travel - spring_inversion_safety_distance pivot_y = wheel_base_y + arm_length pivot_position = Vector(0, pivot_y) pivot_to_spring_anchor_angle = -math.degrees( math.asin((pivot_y - spring_anchor_y) / pivot_to_spring_anchor)) spring_anchor_point = Vector( math.sqrt(pivot_to_spring_anchor**2 - (pivot_y - spring_anchor_y)**2), spring_anchor_y) to_suspension_pivot = spring_anchor_point.x + suspension.arm_pivot_thickness / 2 + vitamins.spring.top_mount_od / 2 + suspension.arm_clearance def get_arm_angle(spring_length): return pivot_to_spring_anchor_angle - \ math.degrees(math.acos((spring_arm_length**2 + pivot_to_spring_anchor**2 - spring_length**2) / (2 * spring_arm_length * pivot_to_spring_anchor))) - \ spring_angle_offset
import codecad from codecad.shapes import * from codecad.util import Vector import parametric import parameters import tools import vitamins import tensioner import suspension import drive_sprocket import track import transmission suspension_pivot_y = 30 tensioner_position = tensioner.pivot_position + Vector(0, suspension_pivot_y) bogie_positions = [ Vector(i * suspension.suspension_spacing + tensioner.to_suspension_pivot, suspension_pivot_y) #for i in range(1)] for i in range(suspension.bogie_count // 2) ] drive_sprocket_position = Vector( bogie_positions[-1].x + drive_sprocket.to_suspension_pivot, suspension_pivot_y) track_clearance = 5 # Distance between track and hull def top_back_cross_frame_member(angle, size, wall_thickness): """ Return a 2D shape with the profile of the frame member going across the tank on the top back side """
def perpendicular(v): """ Perpendicular vector to v (2D only) """ v = v.flattened() return Vector(v.y, -v.x)
def get_spring_anchor_point(spring_arm_length): """ Return the spring anchor point coordinates in 2D relative to arm pivot as codecad Vector. Spring is placed to be at right angle to the arm at full compression. """ return get_spring_point(spring_arm_length, 0) + \ Vector(-math.sin(spring_up_angle), math.cos(spring_up_angle)) * (vitamins.spring.length - vitamins.spring.travel)
def get_spring_point(spring_arm_length, angle): """ Return coordinates of the spring attachment to the arm if the spring is at given angle (angle is between 0 (up position) and travel_angle) """ return Vector(math.cos(spring_up_angle - angle), math.sin(spring_up_angle - angle)) * spring_arm_length
def arm_generator(thickness, pivot_thickness, width, bogie_side_width, arm_length, spring_arm_length, arm_neutral_angle, arm_up_angle, knee_height, knee_angle, pivot_mount_diameter, pivot_mount_height, pivot_mount_screw_head_diameter, pivot_mount_screw_head_countersink, spring_mount_diameter, spring_mount_height, bogie_pivot_mount_diameter, thin_wall, thick_wall, hole_blinding_layer_height): spring_point_angle = spring_up_angle - arm_up_angle bogie_pivot = (arm_length, 0) spring_point = (spring_arm_length * math.cos(spring_point_angle), spring_arm_length * math.sin(spring_point_angle)) knee_mid_angle = math.pi / 2 - arm_neutral_angle assert pivot_mount_height >= spring_mount_height knee_point1 = (bogie_pivot[0] + (knee_height + 0.2 * thickness) * math.cos(knee_mid_angle - math.radians(knee_angle / 2)), bogie_pivot[1] + (knee_height + 0.2 * thickness) * math.sin(knee_mid_angle - math.radians(knee_angle / 2))) knee_point2 = ( bogie_pivot[0] + knee_height * math.cos(knee_mid_angle + math.radians(knee_angle / 2)), bogie_pivot[1] + knee_height * math.sin(knee_mid_angle + math.radians(knee_angle / 2))) _, p1 = util.outer_tangent(Vector(*knee_point1), 0, Vector(0, 0), (pivot_thickness - thickness) / 2) p2, _ = util.outer_tangent(Vector(0, 0), (pivot_thickness - thickness) / 2, Vector(*spring_point), 0) outline = polygon2d([(p1.x, p1.y), knee_point1, bogie_pivot, knee_point2, spring_point, (p2.x, p2.y)]) \ .offset(thickness / 2) outline += circle(d=pivot_thickness) arm = outline.extruded(width + spring_mount_height, symmetrical=False) arm += tools.cone(height=pivot_mount_height - spring_mount_height, upper_diameter=pivot_mount_diameter + 2 * thick_wall, lower_diameter=pivot_thickness, base_height=width + spring_mount_height) spring_mount_top_diameter = thickness / 2 spring_cutout_r0 = spring_mount_height + spring_mount_top_diameter / 2 spring_down_vector = spring_anchor_point - spring_down_point rel_spring_down_angle = math.degrees( math.atan2(spring_down_vector.y, spring_down_vector.x) - arm_down_angle) arm -= spring_cutout_generator(90 + rel_spring_down_angle, spring_cutout_r0, 2 * arm_length, (vitamins.spring.diameter / 2 + arm_clearance)) \ .rotated_z(-90) \ .translated(spring_point[0], spring_point[1], width + vitamins.spring.diameter / 2 + arm_clearance) holes = circle(d=pivot_mount_diameter) + \ circle(d=bogie_pivot_mount_diameter).translated(*bogie_pivot) + \ circle(d=spring_mount_diameter).translated(*spring_point) arm -= holes.extruded(float("inf")) # Pivot screw head countersink arm -= cylinder(d=pivot_mount_screw_head_diameter, h=2 * pivot_mount_screw_head_countersink) if hole_blinding_layer_height: arm += cylinder( d=pivot_mount_screw_head_diameter, h=hole_blinding_layer_height, symmetrical=False).translated_z(pivot_mount_screw_head_countersink) return arm
def get_bogie_wheel_position(angle, side): s = math.sin(angle) c = math.cos(angle) side *= bogie_wheel_spacing / 2 return Vector(c * side + s * bogie_pivot_z, s * side - c * bogie_pivot_z)
bogie_wheel_spacing / 2, wheel_width / 2, wheel_diameter / 2), inner_road_wheel.rotated_x(90).translated( -bogie_wheel_spacing / 2, wheel_width / 2, wheel_diameter / 2), outer_road_wheel.rotated_x(-90).translated( bogie_wheel_spacing / 2, -wheel_width / 2, wheel_diameter / 2), outer_road_wheel.rotated_x(-90).translated( -bogie_wheel_spacing / 2, -wheel_width / 2, wheel_diameter / 2) ] + [vitamins.small_bearing] * 4 + [road_wheel_screw, road_wheel_screw.lock_nut] * 2 + [vitamins.o_ring] * 8) # Y offset of a right suspension arm base in an assembly. arm_base_offset = pivot_guide_length + pivot_screw_head_countersink # Ofset for matching a piece of track with right suspension assembly track_offset = Vector( arm_length * math.cos(arm_neutral_angle), arm_base_offset - arm_width / 2, arm_length * math.sin(arm_neutral_angle) - bogie_pivot_z - wheel_diameter / 2) # Pivot mating surface is at coordinates 0, 0, 0 for both left and right arm # Position of the matching surface for spring anchor point on the right side # This one is rotated in print orientation! spring_anchor_point = Vector( spring_anchor_point.x, spring_anchor_point.y, arm_base_offset - (arm_width + arm_clearance + vitamins.spring.diameter / 2 + vitamins.spring.top_mount_thickness / 2)) def suspension_generator(right, arm_angle=arm_neutral_angle, bogie_angle_fraction=None):