Files
kickdrawers/util.py
2022-06-27 12:12:44 -04:00

572 lines
17 KiB
Python

import numpy as np
import os
import cadquery as cq
from cadquery import exporters as et
from scipy.spatial.distance import pdist, squareform
from items import item_list
from collections import defaultdict
nut_width = 5.5
nut_thickness = 2.5
r_hole = 3.1/2
nut_depth = 8
m_length = 15
def save_stls(objs, base_path, prefix = '', tolerance = 0.1):
try:
os.mkdir(base_path)
except:
pass
num = 0
for itx in objs:
if not isinstance(itx, item_list):
itc = item_list(itx)
else:
itc = itx;
for fkn, ob in itc.as_dict().items():
num += 1
fpath = os.path.join(base_path, prefix + '_' + fkn + '_'+ str(num) + '.stl')
with open(fpath,'w') as wpx:
et.exportShape( ob, et.ExportTypes.STL, wpx, tolerance=tolerance)
def n_gon(r, n_sides, xoff = 0, yoff = 0):
angles = np.linspace(0, 2*np.pi, n_sides+1)
pts = list()
for ang in angles[:-1]:
pts.append(( (r*np.cos(ang)) +xoff, (r*np.sin(ang)) + yoff))
return pts
def bottom_points(w, d, offw = 50, offd = 50, grid = None):
if grid is None:
grid = [2,2]
pts = list()
tl_corners = offw, offd
br_corner = w-offw, d-offd
for xoff in np.linspace(tl_corners[0],br_corner[0],grid[0]):
for yoff in np.linspace(tl_corners[1],br_corner[1],grid[1]):
pts.append( (xoff,yoff))
return pts
def copy(obj):
return obj.translate((0, 0, 0))
def intersect(wp1, wp2):
"""
Return geometric intersection between 2 cadquery.Workplane instances by
exploiting.
A n B = (A u B) - ((A - B) u (B - A))
"""
neg1 = copy(wp1).cut(wp2)
neg2 = copy(wp2).cut(wp1)
negative = neg1.union(neg2)
return copy(wp1).union(wp2).cut(negative)
def xy_wp():
return cq.Workplane('XY')
def xz_wp():
return cq.Workplane('XZ')
def yz_wp():
return cq.Workplane('YZ')
class ListMod(list):
def __iadd__(self, obj):
self.append(obj)
return self
def add_rect(wplane, w, d, offx = 0, offy =0):
return wplane.center(offx, offy).lineTo(0,d).lineTo(w,d).lineTo(w,0).close()
def do_fingers(r_side, f_side, num_tabs = None, swap = False, skip_list = None, axis_step = None, sq_off_mult = 0, sq_dim = None, tab_width = None, cut = True, skip_bolt_1 = False, skip_bolt_2 = False, do_bolt = False, nut_depth_mult = 1, nut_path_mult = 1):
num_tabs = 9 if num_tabs is None else num_tabs
sliver = intersect(r_side, f_side)
pts = list()
for h in sliver.vertices().vals():
pts.append( ( h.X, h.Y, h.Z))
pt=np.asarray(pts)
min_pts = np.min(pt, axis=0)
max_pts = np.max(pt, axis=0)
r_pts = np.asarray([[a.X, a.Y, a.Z] for a in r_side.vertices().vals()])
f_pts = np.asarray([[a.X, a.Y, a.Z] for a in f_side.vertices().vals()])
r_center = np.mean(r_pts, axis=0)
f_center = np.mean(f_pts, axis=0)
# print(r_center)
if axis_step is None:
axis_step = np.argmax(max_pts - min_pts)
axis_sq = list(set([0,1,2]).difference([axis_step]))
axis_not_step = list(set([0,1,2]).difference([axis_step]))
if sq_dim is None:
sq_dim = (max_pts-min_pts)[axis_sq]
total_sz = (max_pts-min_pts)[axis_step]
if tab_width is not None:
num_tabs = int(np.ceil(total_sz/tab_width))
step_sz = total_sz / num_tabs
box_sz = max_pts-min_pts
box_sz[axis_step] /= num_tabs
if skip_list is None:
skip_list = [-1]
tbj = list()
tbh = list()
holes = list()
screw_path = list()
nut_path = list()
for i in range(num_tabs):
xstep = [0,0,0]
if (axis_sq[1]-axis_sq[0]) > 1:
xstep[axis_sq[1]] = 1
flip = True
else:
xstep[axis_sq[0]] = 1
flip = False
normal = [0,0,0]
normal[axis_step] = 1
origin = min_pts.copy()
origin[axis_step] += i * step_sz
t_plane = cq.Plane(tuple(origin), tuple(xstep), tuple(normal))
origin_holes = origin.copy()
origin_holes[axis_step] += step_sz/2
normal_1 = [0,0,0]
normal_1[axis_not_step[0]] = 1
xstep_1 = [0,0,0]
xstep_1[axis_not_step[1]] = 1
normal_2 = [0,0,0]
normal_2[axis_not_step[1]] = 1
xstep_2 = [0,0,0]
xstep_2[axis_not_step[0]] = 1
if swap:
xstep_1, xstep_2 = xstep_2, xstep_1
normal_1, normal_2 = normal_2, normal_1
t_plane_holes_1 = cq.Plane(tuple(origin_holes), tuple(xstep_1), tuple(normal_1))
plane_1_normal = [t_plane_holes_1.zDir.x,t_plane_holes_1.zDir.y,t_plane_holes_1.zDir.z]
t_plane_holes_2 = cq.Plane(tuple(origin_holes), tuple(xstep_2), tuple(normal_2))
plane_2_normal = [t_plane_holes_2.zDir.x,t_plane_holes_2.zDir.y,t_plane_holes_2.zDir.z]
# if swap:
# plane_1_normal, plane_2_normal = plane_2_normal, plane_1_normal
if flip:
tbj.append( add_rect(cq.Workplane(t_plane), sq_dim[1], sq_dim[0]).extrude(step_sz) )
else:
tbj.append( add_rect(cq.Workplane(t_plane), sq_dim[0], sq_dim[1]).extrude(step_sz) )
if flip:
mult = -1
else:
mult = 1;
if do_bolt:
offset_nut_1 = [0,0,0]
offset_nut_2 = [0,0,0]
offset_nut_1[axis_step] = -mult*nut_width/2
offset_nut_2[axis_step] = mult*nut_width/2
offset_nut_1[axis_not_step[0]] = nut_depth_mult* nut_depth
offset_nut_2[axis_not_step[1]] = nut_depth_mult*nut_depth
offset_nut_1 = tuple(offset_nut_1)
offset_nut_2 = tuple(offset_nut_2)
if swap:
offset_nut_1, offset_nut_2 = offset_nut_2, offset_nut_1
def get_closer_dir(origin, dird, vec):
origin = np.asarray(origin)
dird = np.asarray(dird)
vec = np.asarray(vec)
# print(origin)
# print(dird)
# print(vec)
if np.linalg.norm(origin+dird - vec) > np.linalg.norm(origin-dird + vec):
return 1
else:
return -1
h_app = None
s_app = None
n_app = None
if i % 2 == 1 and not skip_bolt_1 :
h_app = cq.Workplane(t_plane_holes_1).pushPoints([[sq_dim[0]/2 + sq_off_mult * r_hole/2,0]]).circle(r_hole).extrude(25)
s_app = add_rect(cq.Workplane(t_plane_holes_1),10, r_hole*2, 0, -r_hole).extrude(nut_path_mult* m_length)
n_app = add_rect(cq.Workplane(t_plane_holes_1), 100, nut_width, 0, 0).extrude(nut_thickness ) .translate(offset_nut_1)
elif i % 2 == 0 and not skip_bolt_2:
h_app = cq.Workplane(t_plane_holes_2).pushPoints([[sq_dim[1]/2 + sq_off_mult* r_hole/2,0]]).circle(r_hole).extrude(25)
s_app = add_rect(cq.Workplane(t_plane_holes_2), 10, 2*r_hole, 0, -r_hole).extrude(nut_path_mult* m_length )
n_app = add_rect(cq.Workplane(t_plane_holes_2), 100, nut_width, 0, 0).extrude(nut_thickness ) .translate(offset_nut_2)
holes.append(h_app)
screw_path.append(s_app)
nut_path.append(n_app)
if cut:
for i in range(num_tabs):
if i in skip_list:
continue
if i % 2 == 0:
r_side = r_side.cut(tbj[i])
if i % 2 == 1:
f_side = f_side.cut(tbj[i])
if do_bolt:
for p in range(num_tabs):
if holes[p] is not None:
if p % 2 == 1:
r_side = r_side.cut(holes[p])
f_side = f_side.cut(screw_path[p])
f_side = f_side.cut( nut_path[p])
if p % 2 == 0:
f_side = f_side.cut(holes[p])
r_side = r_side.cut(screw_path[p])
r_side = r_side.cut(nut_path[p])
return r_side, f_side, [tbj, holes, screw_path, nut_path]
def norm_vector(vec):
return vec / np.linalg.norm(vec)
def vertices_to_array(verts):
pts = list()
for h in verts:
pts.append( (h.X, h.Y, h.Z))
return np.asarray(pts)
def intersect(wp1,wp2):
neg1 = wp1.cut(wp2)
neg2 = wp2.cut(wp1)
negative = neg1.union(neg2)
intersected = wp1.union(wp2).cut(negative)
return intersected
def pround(vals, decimals= 5):
return np.around(vals, decimals = decimals)
def vector_to_array(vector):
return np.asarray([vector.x, vector.y, vector.z])
def workplane_along_vector(v_vec):
most_sig_vec = norm_vector(v_vec)
orth_vector = np.cross(most_sig_vec, np.random.randn(3))
c_work_vec = cq.Workplane(cq.Plane(origin=(0,0,0), xDir=tuple(most_sig_vec), normal = tuple(orth_vector)))
return c_work_vec
def make_fingers(wp1, wp2, num_tabs = 3, r_hole = 3.1/2, slot_depth = 15, nut_width = 5.5, nut_thickness = 2.5, nut_depth = 8, skip_nut_slots_A = False, skip_nut_slots_B = False, swap_ab = False):
sliver = intersect(wp1,wp2)
out = vertices_to_array(sliver.vertices().vals())
dist_mat = squareform(pdist(out))
distances = defaultdict(lambda: dict())
for x1 in range(len(out)):
for x2 in range(len(out)):
distances[np.around(dist_mat[x1,x2], decimals=5)][(x1,x2)] = out[x2] - out[x1]
long_vec = None
long_idces = None
long_arr = None
for dist, ou in distances.items():
ds = np.asarray([y for x,y in ou.items()])
avg_vec = pround(np.mean(np.abs(ds), axis=0))
cross_products = np.cross(ds, avg_vec)
total_mag = np.linalg.norm(np.abs(cross_products))
if dist > 0:
if pround(total_mag) == 0:
long_vec = avg_vec
long_idces = [x for x in ou]
long_arr = ds
vec_dir = norm_vector(long_vec)
wp1_com = cq.Shape.centerOfMass(wp1.objects[0])
wp2_com = cq.Shape.centerOfMass(wp2.objects[0])
selector = cq.DirectionMinMaxSelector(cq.Vector(tuple(vec_dir)))
wp1_top_face_vert = vertices_to_array(wp1.faces(selector).vertices().vals())
wp2_top_face_vert = vertices_to_array(wp2.faces(selector).vertices().vals())
orientations = np.dot(long_arr, long_vec)
face_group_1 = list()
face_group_2 = list()
for idc, val in zip(long_idces, orientations):
if val < 0:
face_group_1.append(idc[0])
face_group_2.append(idc[1])
face_group_1_verts = out[face_group_1]
face_group_2_verts = out[face_group_2]
face_group_1_mean = np.average(face_group_1_verts, axis=0);
face_group_2_mean = np.average(face_group_2_verts, axis=0);
wp1_top_mean = np.average(wp1_top_face_vert, axis=0)
wp2_top_mean = np.average(wp2_top_face_vert, axis=0)
face_to_wp1_top = wp1_top_mean - face_group_1_mean
face_to_wp2_top = wp2_top_mean - face_group_1_mean
dir_to_wp1 = norm_vector( face_to_wp1_top - (np.dot(vec_dir, face_to_wp1_top))*vec_dir )
dir_to_wp2 = norm_vector( face_to_wp2_top - (np.dot(vec_dir, face_to_wp2_top))*vec_dir )
step_d = (face_group_2_mean - face_group_1_mean)/(num_tabs)
origins = list()
for x in range(num_tabs+1):
origins.append(np.average(face_group_1_verts, axis=0) + step_d*x)
objs = list()
step_mag = float(np.linalg.norm(step_d))
csel = cq.DirectionMinMaxSelector(cq.Vector(tuple(step_d)))
cut_objs = list()
for i_st in range(num_tabs):
offset = -step_mag
new_obj = sliver.faces(selector=csel).workplane(offset).split(keepTop=True, keepBottom=True)
new_obj_0 = sliver.newObject([new_obj.objects[0]])
new_obj_1 = sliver.newObject([new_obj.objects[1]])
sliver = new_obj_1
cut_objs.append(new_obj_0)
csel_wp1 = cq.DirectionMinMaxSelector(cq.Vector(tuple(dir_to_wp1)), directionMax=False)
csel_wp2 = cq.DirectionMinMaxSelector(cq.Vector(tuple(dir_to_wp2)), directionMax=False)
csel_wp1_max = cq.DirectionMinMaxSelector(cq.Vector(tuple(dir_to_wp1)), directionMax=True)
csel_wp2_max = cq.DirectionMinMaxSelector(cq.Vector(tuple(dir_to_wp2)), directionMax=True)
def cut_finger(wp1, cobj):
wp1 = wp1.cut(cobj)
return wp1
def cut_screw_slots(wp1, wp2, csel_wp1, csel_wp1_max, cobj, step_d):
os1 = cobj.faces(csel_wp1).workplane()
os1 = os1.cut(cobj)
if np.dot(vector_to_array(os1.plane.zDir), step_d) == 0:
do_swap = True
else:
do_swap = False
os_s = os1.pushPoints([[0,0]]).circle(r_hole).extrude(-10)
wp2 = wp2.cut(os_s)
pa = nut_width
pb = 25
if do_swap:
pa, pb = pb, pa
os1 = cut_objs[idx].faces(csel_wp1_max).workplane()
nut_slot = add_rect(os1.workplane(nut_depth),pa,pb,offx=-pa/2, offy=-pb/2).extrude(nut_thickness)
wp1 = wp1.cut(nut_slot)
na = 2*r_hole
nb = 25
if do_swap:
na, nb = nb, na
os1a = cut_objs[idx].faces(csel_wp1_max).workplane()
screw_slot = add_rect(os1a,na,nb,offx=-na/2, offy=-nb/2).extrude(slot_depth)
wp1 = wp1.cut(screw_slot)
return wp1, wp2, os_s, screw_slot, nut_slot, [os1, os1a]
if swap_ab:
offset = 1
else:
offset = 0
for idx in range(len(cut_objs)):
if (idx+offset) % 2:
wp1 = cut_finger(wp1, cut_objs[idx])
if not skip_nut_slots_A:
wp1, wp2, hole, screw_slot, nut_slot, os1_planes = cut_screw_slots(wp1, wp2, csel_wp1, csel_wp1_max, cut_objs[idx], step_d)
else:
wp2 = cut_finger(wp2, cut_objs[idx])
if not skip_nut_slots_B:
wp2, wp1, hole, screw_slot, nut_slot, os2_planes = cut_screw_slots(wp2, wp1, csel_wp2, csel_wp2_max, cut_objs[idx], step_d)
return wp1, wp2
# def do_fingers(r_side, f_side, num_tabs = 9, tab_width = None, cut = True):
# sliver = intersect(r_side, f_side)
# pts = list()
# for h in sliver.vertices().vals():
# pts.append( ( h.X, h.Y, h.Z))
# pt=np.asarray(pts)
# min_pts = np.min(pt, axis=0)
# max_pts = np.max(pt, axis=0)
# axis_step = np.argmax(max_pts - min_pts)
# axis_sq = list(set([0,1,2]).difference([axis_step]))
# sq_dim = (max_pts-min_pts)[axis_sq]
# total_sz = (max_pts-min_pts)[axis_step]
# if tab_width is not None:
# num_tabs = int(np.ceil(total_sz/tab_width))
# step_sz = total_sz / num_tabs
# box_sz = max_pts-min_pts
# box_sz[axis_step] /= num_tabs
# tbj = list()
# tbh = list()
# for i in range(num_tabs):
# xstep = [0,0,0]
# if (axis_sq[1]-axis_sq[0]) > 1:
# xstep[axis_sq[1]] = 1
# flip = True
# else:
# xstep[axis_sq[0]] = 1
# flip = False
# normal = [0,0,0]
# normal[axis_step] = 1
# origin = min_pts.copy()
# origin[axis_step] += i * step_sz
# t_plane = cq.Plane(tuple(origin), tuple(xstep), tuple(normal))
# if flip:
# tbj.append( add_rect(cq.Workplane(t_plane), sq_dim[1], sq_dim[0]).extrude(step_sz) )
# else:
# tbj.append( add_rect(cq.Workplane(t_plane), sq_dim[0], sq_dim[1]).extrude(step_sz) )
# if cut:
# for i in range(num_tabs):
# if i % 2 == 0:
# r_side = r_side.cut(tbj[i])
# if i % 2 == 1:
# f_side = f_side.cut(tbj[i])
# return r_side, f_side, tbj
def ret_front_pts(bd, bh, spread = 96):
pts = list()
if spread == 0:
pts.append( (bd/2, bh))
else:
pts.append( (bd/2 - spread/2, bh))
pts.append( (bd/2 + spread/2, bh))
return pts
def ret_pts(bd, bh, center = False, max_num = None, spacing = 32):
pts = list()
if center:
ix = int(np.ceil(bd/spacing)/2)
for i in range((-ix+1), ix ):
pts.append((bd/2 + i*spacing, bh))
else:
for i in range(1,int(np.ceil(bd/spacing)) - 1):
pts.append((i*spacing, bh))
pts = tuple(pts)
return pts
def slide_pts(bd, bh, offset_from_back = 54, spacing = (192, 192), reverse = False, which='push-open'):
if which == 'push-open':
pass
elif which == '24inch':
# spacing = (278,201)
spacing = (224,256)
pts = list()
if reverse:
curr_pt = bd-offset_from_back
pts.append([curr_pt, bh])
for spc in spacing:
curr_pt -= spc
pts.append([ curr_pt, bh])
return pts
else:
curr_pt = offset_from_back
pts.append([curr_pt, bh])
for spc in spacing:
curr_pt += spc
pts.append([ curr_pt, bh])
return pts