def vertical_finger(length, thickness, finger_play, amount): # creates _vfa and it's counterpart _vfb # _vfa is starts at 0,0 # _wfb is _wfa offset vertically by one length # takes in the # length = length of the mortise # thickness = thickness of the material # fingerplay = tolerance in length of the finger for smooth fit # amount = amount of fingers for i in range(amount): mortise(length, thickness, finger_play, 0, i * 2 * length + length / 2, rotation=math.pi / 2) simple.active_name("_height_finger") simple.join_multiple("_height_finger") simple.active_name("_vfa") bpy.ops.object.duplicate_move( OBJECT_OT_duplicate={ "linked": False, "mode": 'TRANSLATION' }, TRANSFORM_OT_translate={"value": (0, -length, 0.0)}) simple.active_name("_vfb")
def finger_pair(name, dx=0, dy=0): simple.make_active(name) xpos = (dx / 2) * 1.006 ypos = 1.006 * dy / 2 bpy.ops.object.duplicate_move( OBJECT_OT_duplicate={ "linked": False, "mode": 'TRANSLATION' }, TRANSFORM_OT_translate={"value": (xpos, ypos, 0.0)}) simple.active_name("_finger_pair") simple.make_active(name) bpy.ops.object.duplicate_move( OBJECT_OT_duplicate={ "linked": False, "mode": 'TRANSLATION' }, TRANSFORM_OT_translate={"value": (-xpos, -ypos, 0.0)}) simple.active_name("_finger_pair") simple.join_multiple("_finger_pair") bpy.ops.object.select_all(action='DESELECT') return bpy.context.active_object
def make_variable_flex_pocket(height, finger_thick, pocket_width, locations): # creates pockets pocket using mortise function for kerf bending for dist in locations: mortise(height - 2 * finger_thick, pocket_width, 0, dist, 0, math.pi / 2) simple.active_name("_flex_pocket") simple.join_multiple("_flex_pocket") simple.active_name("flex_pocket")
def fixed_finger(loop, loop_length, finger_size, finger_thick, finger_tolerance, base=False): # distributes mortises of a fixed distance # dynamically changes the finger tolerance with the angle differences # loop = takes in a shapely shape # finger_size = size of the mortise # finger_thick = thickness of the material # finger_tolerance = minimum finger tolerance coords = list(loop.coords) old_mortise_angle = 0 distance = finger_size / 2 j = 0 print("joinery loop length", round(loop_length * 1000), "mm") for i, p in enumerate(coords): if i == 0: p_start = p if p != p_start: not_start = True else: not_start = False pd = loop.project(Point(p)) if not_start: while distance <= pd: mortise_angle = angle(oldp, p) mortise_angle_difference = abs(mortise_angle - old_mortise_angle) mad = (1 + 6 * min(mortise_angle_difference, math.pi / 4) / (math.pi / 4)) # factor for tolerance for the finger if base: mortise(finger_size, finger_thick, finger_tolerance * mad, distance, 0, 0) simple.active_name("_base") else: mortise_point = loop.interpolate(distance) mortise(finger_size, finger_thick, finger_tolerance * mad, mortise_point.x, mortise_point.y, mortise_angle) j += 1 distance = j * 2 * finger_size + finger_size / 2 old_mortise_angle = mortise_angle oldp = p if base: simple.join_multiple("_base") simple.active_name("base") simple.move(x=finger_size) else: simple.join_multiple("_mort") simple.active_name("mortise")
def twist_separator_slot(length, thickness, finger_play=0.00005, percentage=0.5): simple.add_rectangle(thickness + finger_play / 2, length, center_y=False) simple.move(y=((length * percentage - finger_play / 2) / 2)) simple.duplicate() simple.mirrory() simple.join_multiple('simple_rectangle') simple.active_name('_separator_slot')
def make_flex_pocket(length, height, finger_thick, finger_width, pocket_width): # creates pockets pocket using mortise function for kerf bending dist = 3 * finger_width / 2 while dist < length: mortise(height - 2 * finger_thick, pocket_width, 0, dist, 0, math.pi / 2) simple.active_name("_flex_pocket") dist += finger_width * 2 simple.join_multiple("_flex_pocket") simple.active_name("flex_pocket")
def interlock_twist_separator(length, thickness, amount, spacing, edge_distance, finger_play=0.00005, percentage=0.5, start='rounded', end='rounded'): amount -= 1 base_width = 2 * edge_distance + spacing * amount + thickness simple.add_rectangle(base_width, length - finger_play * 2, center_x=False) simple.active_name('_base') twist_separator_slot(length, thickness, finger_play, percentage) while amount > 0: simple.duplicate(x=spacing) amount -= 1 simple.join_multiple('_separator_slot') simple.move(x=edge_distance + thickness / 2) simple.difference('_', '_base') simple.active_name('twist_separator')
def twist_line(length, thickness, finger_play, percentage, amount, distance, center=True): # Makes an amount of twist for the distance and centers it spacing = distance / amount while amount > 0: position = spacing * amount interlock_twist(length, thickness, finger_play, percentage=percentage, cx=position) print('twistline', amount, distance, position) amount -= 1 simple.join_multiple('_groove') simple.active_name('twist_line') if center: simple.move(x=(-distance - spacing) / 2)
def horizontal_finger(length, thickness, finger_play, amount, center=True): # creates _wfa counterpart _wfb # _wfa is centered at 0,0 # _wfb is _wfa offset by one length # takes in the # length = length of the mortise # thickness = thickness of the material # fingerplay = tolerance in length of the finger for smooth fit if center: for i in range(amount): if i == 0: mortise(length, thickness, finger_play, 0, thickness / 2) simple.active_name("_width_finger") else: mortise(length, thickness, finger_play, i * 2 * length, thickness / 2) simple.active_name("_width_finger") mortise(length, thickness, finger_play, -i * 2 * length, thickness / 2) simple.active_name("_width_finger") else: for i in range(amount): mortise(length, thickness, finger_play, length / 2 + 2 * i * length, 0) simple.active_name("_width_finger") simple.join_multiple("_width_finger") simple.active_name("_wfa") bpy.ops.object.duplicate_move( OBJECT_OT_duplicate={ "linked": False, "mode": 'TRANSLATION' }, TRANSFORM_OT_translate={"value": (length, 0.0, 0.0)}) simple.active_name("_wfb")
def open_curve(line, thick, diameter, tolerance, amount=0, stem=1, twist=False, t_neck=0.5, t_thick=0.01, twist_amount=1, which='MF', twist_keep=False): # puts puzzle connectors at the end of an open curve # optionally puts twist lock connectors at the puzzle connection # optionally puts twist lock connectors along the open curve # line = shapely linestring # thick = thickness of the bar # diameter = diameter of the tool for joint creation # tolerance = Tolerance in the joint # amount = amount of fingers in the joint 0 means auto generate # stem = amount of radius the stem or neck of the joint will have # twist = twist lock addition # twist_amount = twist amount distributed on the curve not counting the joint twist locks # tneck = percentage the twist neck will have compared to thick # tthick = thicknest of the twist material # Which M,F, MF, MM, FF coords = list(line.coords) start_angle = joinery.angle(coords[0], coords[1]) + math.pi/2 end_angle = joinery.angle(coords[-1], coords[-2]) + math.pi/2 p_start = coords[0] p_end = coords[-1] print('start angle', start_angle) print('end angle', end_angle) bpy.ops.curve.simple(align='WORLD', location=(0, 0, 0), rotation=(0, 0, 0), Simple_Type='Rectangle', Simple_width=thick*2, Simple_length=thick * 2, use_cyclic_u=True, edit_mode=False, shape='3D') simple.active_name('tmprect') simple.move(y=thick) simple.duplicate() simple.rotate(start_angle) simple.move(x=p_start[0], y=p_start[1]) simple.make_active('tmprect') simple.rotate(end_angle) simple.move(x=p_end[0], y=p_end[1]) simple.union('tmprect') dilated = line.buffer(thick/2) # expand shapely object to thickness utils.shapelyToCurve('tmp_curve', dilated, 0.0) simple.difference('tmp', 'tmp_curve') # truncate curve at both ends with the rectangles fingers(diameter, tolerance, amount, stem=stem) simple.make_active('fingers') simple.rotate(end_angle) simple.move(x=p_end[0], y=p_end[1]) simple.active_name('tmp_fingers') simple.union('tmp_') simple.active_name('tmp_curve') twistm('tmp_curve', thick, diameter, tolerance, twist, t_neck, t_thick, end_angle, x=p_end[0], y=p_end[1], twist_keep=twist_keep) twistf('receptacle', thick, diameter, tolerance, twist, t_neck, t_thick, twist_keep=twist_keep) simple.rename('receptacle', 'tmp') simple.rotate(start_angle+math.pi) simple.move(x=p_start[0], y=p_start[1]) simple.difference('tmp', 'tmp_curve') if twist_keep: simple.make_active('twist_keep_f') simple.rotate(start_angle + math.pi) simple.move(x=p_start[0], y=p_start[1]) if twist_amount > 0 and twist: twist_start = line.length / (twist_amount+1) joinery.distributed_interlock(line, line.length, thick, t_thick, tolerance, twist_amount, tangent=math.pi/2, fixed_angle=0, start=twist_start, end=twist_start, closed=False, type='TWIST', twist_percentage=t_neck) if twist_keep: simple.duplicate() simple.active_name('twist_keep') simple.join_multiple('twist_keep') simple.make_active('interlock') simple.active_name('tmp_twist') simple.difference('tmp', 'tmp_curve') simple.active_name('puzzle_curve')
def distributed_interlock(loop, loop_length, finger_depth, finger_thick, finger_tolerance, finger_amount, tangent=0, fixed_angle=0, start=0.01, end=0.01, closed=True, type='GROOVE', twist_percentage=0.5): # distributes interlocking joints of a fixed amount # dynamically changes the finger tolerance with the angle differences # loop = takes in a shapely shape # finger_size = size of the mortise # finger_thick = thickness of the material # finger_tolerance = minimum finger tolerance # twist_percentage = portion of twist finger which is the stem coords = list(loop.coords) print(closed) if not closed: spacing = (loop_length - start - end) / (finger_amount - 1) distance = start end_distance = loop_length - end else: spacing = loop_length / finger_amount distance = 0 end_distance = loop_length j = 0 print("joinery loop length", round(loop_length * 1000), "mm") print("distance between joints", round(spacing * 1000), "mm") for i, p in enumerate(coords): if i == 0: p_start = p if p != p_start: not_start = True else: not_start = False pd = loop.project(Point(p)) if not_start: while distance <= pd and end_distance >= distance: if fixed_angle == 0: groove_angle = angle(oldp, p) + math.pi / 2 + tangent else: groove_angle = fixed_angle groove_point = loop.interpolate(distance) print(j, "groove_angle", round(180 * groove_angle / math.pi), "distance", round(distance * 1000), "mm") single_interlock(finger_depth, finger_thick, finger_tolerance, groove_point.x, groove_point.y, groove_angle, type, twist_percentage=twist_percentage) j += 1 distance = j * spacing + start oldp = p simple.join_multiple("_groove") simple.active_name("interlock")
def variable_finger(loop, loop_length, min_finger, finger_size, finger_thick, finger_tolerance, adaptive, base=False, double_adaptive=False): # distributes mortises of a fixed distance # dynamically changes the finger tolerance with the angle differences # loop = takes in a shapely shape # finger_size = size of the mortise # finger_thick = thickness of the material # finger_tolerance = minimum finger tolerance # adaptive = angle threshold to reduce finger size coords = list(loop.coords) old_mortise_angle = 0 distance = min_finger / 2 finger_sz = min_finger oldfinger_sz = min_finger hpos = [] # hpos is the horizontal positions of the middle of the mortise # slope_array(loop) print("joinery loop length", round(loop_length * 1000), "mm") for i, p in enumerate(coords): if i == 0: p_start = p if p != p_start: not_start = True else: not_start = False pd = loop.project(Point(p)) if not_start: while distance <= pd: mortise_angle = angle(oldp, p) mortise_angle_difference = abs(mortise_angle - old_mortise_angle) mad = (1 + 6 * min(mortise_angle_difference, math.pi / 4) / (math.pi / 4)) # factor for tolerance for the finger distance += mad * finger_tolerance # move finger by the factor mad greater with larger angle difference mortise_point = loop.interpolate(distance) if mad > 2 and double_adaptive: hpos.append(distance) # saves the mortise center hpos.append(distance + finger_sz) # saves the mortise center if base: mortise(finger_sz, finger_thick, finger_tolerance * mad, distance + finger_sz, 0, 0) simple.active_name("_base") else: mortise(finger_sz, finger_thick, finger_tolerance * mad, mortise_point.x, mortise_point.y, mortise_angle) if i == 1: # put a mesh cylinder at the first coordinates to indicate start simple.remove_multiple("start_here") bpy.ops.mesh.primitive_cylinder_add( radius=finger_thick / 2, depth=0.025, enter_editmode=False, align='WORLD', location=(mortise_point.x, mortise_point.y, 0), scale=(1, 1, 1)) simple.active_name("start_here_mortise") old_distance = distance old_mortise_point = mortise_point finger_sz = finger_size next_angle_difference = math.pi # adaptive finger length start while finger_sz > min_finger and next_angle_difference > adaptive: finger_sz *= 0.95 # reduce the size of finger by a percentage... the closer to 1.0, the slower distance = old_distance + 3 * oldfinger_sz / 2 + finger_sz / 2 mortise_point = loop.interpolate( distance) # get the next mortise point next_mortise_angle = angle( (old_mortise_point.x, old_mortise_point.y), (mortise_point.x, mortise_point.y)) # calculate next angle next_angle_difference = abs(next_mortise_angle - mortise_angle) oldfinger_sz = finger_sz old_mortise_angle = mortise_angle oldp = p if base: simple.join_multiple("_base") simple.active_name("base") else: print("placeholder") simple.join_multiple("_mort") simple.active_name("variable_mortise") return hpos
def gear(mm_per_tooth=0.003, number_of_teeth=5, hole_diameter=0.003175, pressure_angle=0.3488, clearance=0.0, backlash=0.0, rim_size=0.0005, hub_diameter=0.006, spokes=4): simple.deselect() pi = math.pi p = mm_per_tooth * number_of_teeth / pi / 2 # radius of pitch circle c = p + mm_per_tooth / pi - clearance # radius of outer circle b = p * math.cos(pressure_angle) # radius of base circle r = p - (c - p) - clearance # radius of root circle t = mm_per_tooth / 2 - backlash / 2 # tooth thickness at pitch circle k = -gear_iang( b, p ) - t / 2 / p # angle to where involute meets base circle on each side of tooth shapely_gear = Polygon([(0, 0), gear_polar(r, k if r < b else -pi / number_of_teeth), gear_q7(0, r, b, c, k, 1), gear_q7(0.1, r, b, c, k, 1), gear_q7(0.2, r, b, c, k, 1), gear_q7(0.3, r, b, c, k, 1), gear_q7(0.4, r, b, c, k, 1), gear_q7(0.5, r, b, c, k, 1), gear_q7(0.6, r, b, c, k, 1), gear_q7(0.7, r, b, c, k, 1), gear_q7(0.8, r, b, c, k, 1), gear_q7(0.9, r, b, c, k, 1), gear_q7(1.0, r, b, c, k, 1), gear_q7(1.0, r, b, c, k, -1), gear_q7(0.9, r, b, c, k, -1), gear_q7(0.8, r, b, c, k, -1), gear_q7(0.7, r, b, c, k, -1), gear_q7(0.6, r, b, c, k, -1), gear_q7(0.5, r, b, c, k, -1), gear_q7(0.4, r, b, c, k, -1), gear_q7(0.3, r, b, c, k, -1), gear_q7(0.2, r, b, c, k, -1), gear_q7(0.1, r, b, c, k, -1), gear_q7(0.0, r, b, c, k, -1), gear_polar(r, -k if r < b else pi / number_of_teeth)]) utils.shapelyToCurve('tooth', shapely_gear, 0.0) i = number_of_teeth while i > 1: simple.duplicate() simple.rotate(2 * math.pi / number_of_teeth) i -= 1 simple.join_multiple('tooth') simple.active_name('_teeth') bpy.ops.curve.simple(align='WORLD', location=(0, 0, 0), rotation=(0, 0, 0), Simple_Type='Circle', Simple_radius=r, shape='3D', use_cyclic_u=True, edit_mode=False) simple.active_name('_hub') simple.union('_') simple.active_name('_gear') simple.remove_doubles() if spokes > 0: bpy.ops.curve.simple(align='WORLD', location=(0, 0, 0), rotation=(0, 0, 0), Simple_Type='Circle', Simple_radius=r - rim_size, shape='3D', use_cyclic_u=True, edit_mode=False) simple.active_name('_hole') simple.difference('_', '_gear') bpy.ops.curve.simple(align='WORLD', location=(0, 0, 0), rotation=(0, 0, 0), Simple_Type='Circle', Simple_radius=hub_diameter / 2, shape='3D', use_cyclic_u=True, edit_mode=False) simple.active_name('_hub') bpy.ops.curve.simple(align='WORLD', location=(0, 0, 0), rotation=(0, 0, 0), Simple_Type='Circle', Simple_radius=hole_diameter / 2, shape='3D', use_cyclic_u=True, edit_mode=False) simple.active_name('_hub_hole') simple.difference('_hub', '_hub') simple.join_multiple('_') simple.add_rectangle( r - rim_size - ((hub_diameter - hole_diameter) / 4 + hole_diameter / 2), hub_diameter / 2, center_x=False) simple.move(x=(hub_diameter - hole_diameter) / 4 + hole_diameter / 2) simple.active_name('_spoke') angle = 2 * pi / spokes while spokes > 0: simple.duplicate() simple.rotate(angle) spokes -= 1 simple.union('_spoke') simple.remove_doubles() simple.union('_') else: bpy.ops.curve.simple(align='WORLD', location=(0, 0, 0), rotation=(0, 0, 0), Simple_Type='Circle', Simple_radius=hole_diameter, shape='3D', use_cyclic_u=True, edit_mode=False) simple.active_name('_hole') simple.difference('_', '_gear') name = 'gear-' + str(round(mm_per_tooth * 1000, 1)) name += 'mm-pitch-' + str(number_of_teeth) name += 'teeth-PA-' + str(round(math.degrees(pressure_angle), 1)) simple.active_name(name)