Skip to content

Instantly share code, notes, and snippets.

@RichardPotthoff
Created April 17, 2024 19:31
Show Gist options
  • Select an option

  • Save RichardPotthoff/0b9d624e5503d78e3d19541babcd9c72 to your computer and use it in GitHub Desktop.

Select an option

Save RichardPotthoff/0b9d624e5503d78e3d19541babcd9c72 to your computer and use it in GitHub Desktop.
generate g-code for a capstan
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Blank Factory"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Common Subroutines"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### plotArc, plotArcchain, calcTangent, pipe, iterize"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"jupyter": {
"source_hidden": true
}
},
"outputs": [],
"source": [
"import numpy as np\n",
"from matplotlib import pyplot as plt\n",
"from cmath import pi,acos,exp,sqrt\n",
"def plotArc(ax,P0,n0,l,da,*args,tol=0.001,**kwargs):\n",
" if l==0:\n",
" return\n",
" x=np.linspace(0,l,max(2,int(abs(6*(da/(2*pi)))),int(l//(2*abs(2*l/da*tol)**0.5)+1))if (da!=0) and (l!=0) else 2)\n",
" phi2=x/l*da/2\n",
" p=P0+x*np.sinc(phi2/pi)*n0*np.exp(1j*phi2)\n",
" ax.plot(p.real,p.imag,*args,**kwargs)\n",
" \n",
"def plotArcchain(ax,P0,n0,arcs,*args,**kwargs):\n",
" p=P0\n",
" n=n0\n",
" for l,da in arcs:\n",
" plotArc(ax,p,n,l,da,*args,**kwargs)\n",
" p+=l*np.sinc(da/(2*pi))*n*exp(1j*da/2)\n",
" n*=exp(1j*da)\n",
" \n",
"def calcTangent(c1,r1,c2,r2):\n",
" c2_c1=(c2-c1)\n",
" lcc=abs(c2_c1) \n",
" ec2_c1=c2_c1/lcc\n",
" cosphi=((r1+r2)/lcc)\n",
" phi=-cosphi+1j*(1-cosphi**2)**0.5\n",
" t1=c1-r1*ec2_c1*phi\n",
" t2=c2+r2*ec2_c1*phi\n",
" return [t1,t2]\n",
" \n",
" \n",
"def pipe(*f):#reverses the order of chained function calls: f3(f2(f1(x))) = pipe(f1,f2,f3)(x)\n",
" if len(f)==0:\n",
" def g(*x):\n",
" return None\n",
" else:\n",
" def g(*x):\n",
" x=f[0](*x)\n",
" for fi in f[1:]:\n",
" if fi==None:\n",
" continue\n",
" x=fi(x)\n",
" return x\n",
" return g\n",
" \n",
" \n",
"def iterize(f):\n",
" if f==None:\n",
" def wrapper(*args,**kwargs):\n",
" if len(args)==0:\n",
" return None\n",
" else:\n",
" return args[0]\n",
" else: \n",
" def wrapper(*args,**kwargs):\n",
" if len(args)==0:\n",
" return f(**kwargs)\n",
" else: \n",
" t=args[0]\n",
" return (f(t_,*args[1:],**kwargs) for t_ in t) if hasattr(t,'__next__') else f(*args,**kwargs)\n",
" wrapper.__name__='iterized_'+f.__name__\n",
" return wrapper"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### NamedList"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"jupyter": {
"source_hidden": true
}
},
"outputs": [],
"source": [
"class NamedList(list): \n",
" '''\n",
" A mutable version of namedtuple.\n",
" '''\n",
" def __init__(self,*args,**kwargs):\n",
" super().extend(args)\n",
" super().__setattr__('_lookup',dict())\n",
" self._lookup.update({ f'_{i}':i for i in range(len(self))})\n",
" self.update(**kwargs)\n",
" def __setattr__(self,name,value):\n",
" self.update(**{name:value})\n",
" def __getattr__(self,name):\n",
" try: \n",
" return self.__getitem__(self._lookup[name])\n",
" except:\n",
" raise AttributeError(f\"Found no attribute named '{name}'. \")\n",
" def append(self,value):\n",
" raise IndexError(f\"Use the 'update' method to add elements to a {self.__class__.__name}!\") \n",
" def extend(self,value):\n",
" raise IndexError(f\"Use the 'update' method to add elements to a {self.__class__.__name}!\") \n",
" def update(self,*args,**kwargs): \n",
" if len(args)>0:\n",
" if len(args)>len(self):\n",
" self[:]=args[:len(self)]\n",
" self._lookup.update({f'_{i}':i for i in range(len(self),len(args))})\n",
" super().extend(args[len(self):])\n",
" else:\n",
" self[:len(args)]=args\n",
" for name,value in kwargs.items():\n",
" if not name in self._lookup:\n",
" if name[0]=='_':\n",
" new_index=int(name[1:])\n",
" self._lookup.update({f'_{i}':i for i in range(len(self),new_index+1)})\n",
" super().extend([None]*(new_index-len(self)+1))\n",
" self[new_index]=value\n",
" KeyError(f\"Error in 'update': Field '{name}' does not exist.\")\n",
" else:\n",
" self._lookup[f'_{len(self)}']=len(self)\n",
" self._lookup[name]=len(self)\n",
" super().append(value)\n",
" else:\n",
" self.__setitem__(self._lookup[name],value)\n",
" return self\n",
" def alias(self,aliases):\n",
" for name,alias in aliases.items():\n",
" if type(alias)!=str:\n",
" raise KeyError(f\"Error in 'alias': Field name '{alias}' must be a valid variable name.\")\n",
" if alias[0]=='_':\n",
" raise KeyError(f\"Error in 'alias': Field name '{alias}' must not start with an underscore ('_').\")\n",
" if alias in self._lookup:\n",
" if self._lookup[alias]==self.lookup[name]:\n",
" return\n",
" else:\n",
" raise KeyError(f\"The alias name '{alias}' is already used for a different field.\")\n",
" self._lookup[alias]=self._lookup[name]\n",
" def rename(self,substitutions):\n",
" for old_name,new_name in substitutions.items():\n",
" if old_name[0]=='_':\n",
" raise KeyError(f\"The name '{old_name}' cannot be renamed.\")\n",
" \n",
" if (new_name!=None) and (new_name[0]=='_'):\n",
" raise KeyError(f\"Error in 'rename': Field name '{new_name}' must not start with an underscore ('_').\")\n",
" old_index=self._lookup.pop(old_name,None)\n",
" if old_index==None:\n",
" raise KeyError(f\"The name '{old_name}' does not exist.\")\n",
" if new_name in self._lookup:\n",
" if self._lookup[new_name]==old_index:\n",
" return\n",
" else:\n",
" raise KeyError(f\"The name '{alias}' is already used for a different member of the list.\")\n",
" if new_name!=None:\n",
" self._lookup[new_name]=old_index\n",
" def __repr__(self):\n",
" args=\", \".join(f'{key}={value}' for key,value in self.as_dict().items())\n",
" return f'{self.__class__.__name__}({args})' \n",
" def as_dict(self):\n",
" key_for_index={index:key for key,index in self._lookup.items()}\n",
" return {key_for_index.get(index,index):value for index,value in enumerate(self)}\n",
" def copy(self):\n",
" myCopy=self.__class__.from_list(self)\n",
" myCopy._lookup.update(self._lookup)\n",
" return myCopy"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"jupyter": {
"source_hidden": true
}
},
"outputs": [
{
"data": {
"text/plain": [
"NamedList(_0=1, _1=2, _2=3, _3=10, _4=56)"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"nl=NamedList(1,2,3,10,56)\n",
"nl\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"jupyter": {
"source_hidden": true
}
},
"outputs": [],
"source": [
"class dataobj(object):\n",
" def __init__(self,*args,**kwargs):\n",
" self.__dict__.update(**kwargs)\n",
" def __getitem__(self,name):\n",
" return self.__dict__[name]\n",
" def __setitem__(self,name,value):\n",
" if type(name)!=str: raise NameError()\n",
" self.__dict__[name]=value\n",
" @property\n",
" def x(self):\n",
" if not hasattr(self,'p'): raise AttributeError(\"'dataobj' object has no attribute 'x'\")\n",
" return self.p.real\n",
" @x.setter\n",
" def x(self,value):\n",
" if not hasattr(self,'p'): self.p=0.0+0.0j\n",
" self.p=value+1j*self.p.imag\n",
" \n",
" @property \n",
" def y(self):\n",
" if not hasattr(self,'p'): raise AttributeError(\"'dataobj' object has no attribute 'y'\")\n",
" return self.p.imag\n",
" @y.setter\n",
" def y(self,value):\n",
" if not hasattr(self,'p'): self.p=0.0+0.0j\n",
" self.p=self.p.real+1j*value"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"jupyter": {
"source_hidden": true
}
},
"outputs": [],
"source": [
"from bisect import bisect_right\n",
"\n",
"def binary_search(a, x, lo=0, hi=None): # can't use a to specify default for hi\n",
" hi = hi if hi is not None else len(a) # hi defaults to len(a) \n",
" pos = bisect_left(a,x,lo,hi) # find insertion position\n",
" return pos # don't walk off the end\n",
"def index_frac(x,ax):\n",
" idx=bisect_right(ax,x,1,len(ax)-1)\n",
" return idx-1,(x-ax[idx-1])/(ax[idx]-ax[idx-1])\n",
"def interp(x,ax,ay):\n",
" idx=bisect_right(ax,x,1,len(ax)-1)\n",
" return ay[idx-1]+(x-ax[idx-1])/(ax[idx]-ax[idx-1])*(ay[idx]-ay[idx-1])\n",
"def cumsum(x,x_start=None):\n",
" if x_start!=None:\n",
" csum=[x_start]+x.copy()\n",
" else:\n",
" csum=x.copy()\n",
" for i in range(1,len(csum)):\n",
" csum[i]+=csum[i-1]\n",
" return csum\n",
" "
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"jupyter": {
"source_hidden": true
}
},
"outputs": [],
"source": [
"#interpSegments, Segments2Complex\n",
"import numpy as np\n",
"from matplotlib import pyplot as plt\n",
"from numpy import pi,exp,sign,inf\n",
"\n",
"def polygonArea(p):\n",
" def crossprod(v1,v2):\n",
" return v1.real*v2.imag-v2.real*v1.imag\n",
" return 0.5*np.sum(crossprod(p[range(-1,len(p)-1)],p))\n",
"\n",
"def SegmentsLength(Segs):\n",
" return sum(l for l,*_ in Segs)\n",
"\n",
"def SegmentsArea(Segs):\n",
" nSegs=len(Segs)\n",
" dl,dang,*opts=np.array(Segs).transpose()\n",
" ang=np.cumsum(dang)\n",
" ang=exp(1j*np.insert( ang,0,0))\n",
" dang_2=np.exp(1j*dang/2)\n",
" viSeg=np.sinc(dang/(2*pi))*dl*dang_2*ang[:-1]\n",
" pSeg=np.cumsum(viSeg)\n",
" olderr=np.geterr()\n",
" np.seterr(divide='ignore',invalid='ignore')#suppress the warnings from 0/0 = nan. nansum assumes nan=0, which is the correct value in this case\n",
" area=polygonArea(pSeg) + np.nansum((dl/dang)**2*(dang/2.0-dang_2.real*dang_2.imag))\n",
" np.seterr(**olderr)\n",
" return area\n",
"\n",
"def InterpSegments(Segs,t,p0=0.+0.j,a0=0+1j,scale=1.0,eps=1e-6):\n",
" \"\"\"\n",
" Segment points are calculated for values of 't', where 't' is the normalized\n",
" length of the path. t is in the range of [0..1[\n",
" \"\"\"\n",
" dl,dang=np.array([(l,a) for l,a,*_ in Segs]).transpose()\n",
" L=np.cumsum(np.insert(dl,0,0.0))\n",
" ang_=np.cumsum(np.insert(dang,0,0.0))\n",
" ang=exp(1j*ang_)\n",
" viSeg=np.sinc(dang/(2*pi))*dl*scale*np.exp(1j*dang/2)*ang[:-1]\n",
" pSeg=np.cumsum(np.insert(viSeg,0,0+0j))\n",
" if not hasattr(t,'__getitem__'): #not an array\n",
" t=np.array([t]) #convert to array\n",
" else:\n",
" if t.shape==(): #no dimensions\n",
" t=np.array([t])\n",
" else:\n",
" t=np.array(t)\n",
" T=t.astype(int)\n",
"# t=t-T\n",
" if ((abs(pSeg[-1])<eps) and (abs(ang[-1]-(1+0j))<eps)):\n",
" pr,ar=np.zeros((len(t),),dtype=complex), np.ones((len(t),),dtype=complex) # closed loop. No translation/rotation necessary for t>1\n",
" else: #endpoint of path != startpoint => repeat path for t>1 by translating and rotating it\n",
" def rotateSecant(v,beta,T):\n",
" beta2=beta/2\n",
" rot2=exp(1j*beta2)\n",
" uniqueT,inverseIndex=np.unique(T,return_inverse=True) #don't re-calculate for identical values of T\n",
" p=(v*rot2**(uniqueT-1)/nsinc(beta2/np.pi) * uniqueT * np.sinc(uniqueT*beta2/np.pi))[inverseIndex]\n",
" a=(rot2**(2*uniqueT))[inverseIndex]\n",
" return p,a\n",
" pr,ar=rotateSecant(pSeg[-1]*a0,ang_[-1],T)\n",
" pr+=p0\n",
" ar*=a0\n",
" l=L/L[-1]\n",
" Xx=np.interp(t-T,l,range(len(l)))\n",
" X=np.maximum(0,np.minimum(Xx.astype(int),len(dang)-1)) #segment index\n",
" x=Xx-X#within seggment\n",
" p=pSeg[X] + np.sinc( dang[X]*x /(2*pi))* dl[X]*x *scale*np.exp(1j* dang[X]*x /2)*ang[X]\n",
" p=p*ar+pr\n",
" a=ang[X]*np.exp(1j*dang[X]*x)*ar\n",
" if len(p)==1:\n",
" p=p[0] #convert array to single value if argument was a single value\n",
" a=a[0]\n",
" return p,a,L[-1]*t,X\n",
"\n",
"def Segments2Complex(Segs,p0=0.+0.j,scale=1.0,a0=0+1j,tol=0.05,offs=0,loops=1,return_start=False):\n",
" \"\"\"\n",
" The parameter \"tol defines the resolution. It is the maximum allowable\n",
" difference between circular arc segment, and the secant between the\n",
" calculated points on the arc. Smaller values for tol will result in\n",
" more points per segment.\n",
" \"\"\"\n",
" a=a0\n",
" p=p0\n",
" p-=1j*a*offs\n",
" L=0\n",
" if return_start:\n",
" yield p,a,L,-1 #assuming closed loop: start-point = end-point\n",
" loopcount=0\n",
" while (loops==None) or (loops==inf) or (loopcount<loops):\n",
" loopcount+=1\n",
" for X,(l,da,*_) in enumerate(Segs):\n",
" l=l*scale\n",
" if da!=0:\n",
" r=l/da\n",
" r+=offs\n",
" if r!=0:\n",
" l=r*da\n",
" dl=2*abs(2*r*tol)**0.5\n",
" n=max(int(abs(6*(da/(2*pi)))),int(l//dl)+1)\n",
" else:\n",
" n=1\n",
" dda=exp(1j*da/n)\n",
" dda2=dda**0.5\n",
" v=(2*r*dda2.imag)*dda2*a\n",
" else:\n",
" n=1\n",
" dda=1\n",
" v=l*a\n",
" for i in range(n):\n",
" L+=l/n\n",
" p+=v\n",
" yield p,a,L,X\n",
" v*=dda\n",
" a*=dda"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"jupyter": {
"source_hidden": true
}
},
"outputs": [],
"source": [
"import math, cmath\n",
"def sinc(alpha):\n",
" return 1.0 if alpha==0 else math.sin(alpha)/alpha\n",
"def arcChainInterpolator(t=None,/,*,arcChain,p0=0.+0.j,a0=0+1j,scale=1.0,eps=1e-6):\n",
" \"\"\"\n",
" Segment points are calculated for values of 't', where 't' is the normalized\n",
" length of the path. t is in the range of [0..1[\n",
" \"\"\"\n",
" dl,dang=np.array([(l,a) for l,a,*_ in arcChain]).transpose()\n",
" L=np.cumsum(np.insert(dl,0,0.0))\n",
" l=L/L[-1]\n",
" ang_=np.cumsum(np.insert(dang,0,0.0))\n",
" ang=exp(1j*ang_)\n",
" viSeg=np.sinc(dang/(2*pi))*dl*scale*np.exp(1j*dang/2)*ang[:-1]\n",
" pSeg=np.cumsum(np.insert(viSeg,0,0+0j))\n",
" viSeg=list(viSeg)\n",
" pSeg=list(pSeg)\n",
" dang=list(dang)\n",
" ang_=list(ang_)\n",
" ang=list(ang)\n",
" L=list(L)\n",
" l=list(l)\n",
" l_idx=list(range(len(l)))\n",
" def interpolateArcChain(t,/):\n",
" T=int(t)\n",
" # t=t-T\n",
" if ((abs(pSeg[-1])<eps) and (abs(ang[-1]-(1+0j))<eps)):\n",
" pr,ar=0.0+0.0j,1.0+0.0j\n",
" else: #endpoint of path != startpoint => repeat path for t>1 by translating and rotating it\n",
" def rotateSecant(v,beta,T):\n",
" beta2=beta/2\n",
" rot2=exp(1j*beta2)\n",
" p=(v*rot2**(T-1)/sinc(beta2) * T * sinc(T*beta2))\n",
" a=(rot2**(2*T))\n",
" return p,a\n",
" pr,ar=rotateSecant(pSeg[-1]*a0,ang_[-1],T)\n",
" pr+=p0\n",
" ar*=a0\n",
"# Xx=np.interp(t-T,l,l_idx)\n",
"# X=max(0,min(int(Xx),len(dang)-1)) #segment index\n",
"# x=Xx-X#within seggment\n",
" X,x=index_frac(t-T,l)\n",
" p=pSeg[X] + sinc( dang[X]*x/2)* dl[X]*x *scale*cmath.exp(1j* dang[X]*x /2)*ang[X]\n",
" p=p*ar+pr\n",
" a=ang[X]*cmath.exp(1j*dang[X]*x)*ar\n",
" return p,a,L[-1]*t,X\n",
" return interpolateArcChain\n",
"# return interpolateArcChain(t) if t!=None else interpolateArcChain"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"jupyter": {
"source_hidden": true
}
},
"outputs": [],
"source": [
"def ISO_thread(z=None,/,phi=0.0,*,Pitch,External=False):\n",
" tan60=3**0.5\n",
" cos60=0.5\n",
" sin60=tan60*cos60\n",
" Pitch2=Pitch/2\n",
" H=(Pitch2)*tan60\n",
" flank_start=Pitch2/8\n",
" r_maj=flank_start/sin60\n",
" r_maj2=r_maj**2\n",
" c_maj=-flank_start/tan60\n",
" r_min=2*r_maj \n",
" r_min2=r_min**2\n",
" c_min=-5/8*H+Pitch2/4/tan60\n",
" flank_end=(3/4)*Pitch2\n",
" if External:\n",
" def ISO_thread_(z,/,phi=phi,*_,**__):\n",
" dz=abs((z-phi*Pitch+Pitch2)%Pitch-Pitch2)#use symmetries\n",
" if dz<flank_start:\n",
" return 0.0\n",
" if dz<=flank_end:\n",
" return -tan60*(dz-flank_start)\n",
" return (c_min-(r_min2-(Pitch2-dz)**2)**0.5)\n",
" else:# internal thread\n",
" def ISO_thread_(z,/,phi=phi,*_,**__):\n",
" dz=abs((z-phi*Pitch+Pitch2)%Pitch-Pitch2)#use symmetries\n",
" if dz<flank_start:\n",
" return c_maj+(r_maj2-dz**2)**0.5\n",
" if dz<=flank_end:\n",
" return -tan60*(dz-flank_start)\n",
" return -5/8*H\n",
" return ISO_thread_(z) if z!= None else ISO_thread_\n"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"jupyter": {
"source_hidden": true
}
},
"outputs": [],
"source": [
"def arcsFromSpec_(*,R_rim, R_hub, n_spokes, n_strands,phi_rim, r_fillet_rim, phi_hub, r_fillet_hub):\n",
" import cmath\n",
" from cmath import pi\n",
" #geometry of the mesh:\n",
" phi_tot=2*pi*n_strands\n",
" phi_spoke2=phi_tot/(2*n_spokes)#angle be\n",
" l1,r1,l2,r2=phi_rim*phi_spoke2*R_rim,r_fillet_rim*phi_spoke2*R_rim,phi_hub*phi_spoke2*R_hub,r_fillet_hub*phi_spoke2*R_hub\n",
" c1=(0-1j*(R_rim-r1))*cmath.exp(1j*phi_rim*phi_spoke2)#center of the rim fillet\n",
" c2=(0-1j*(R_hub+r2))*cmath.exp(1j*((1-phi_hub)*phi_spoke2))#center of the hub fillet\n",
" t1,t2=calcTangent(c1,r1,c2,r2)#end points of tangent between fillet1 and fillet 2\n",
" ltan,phitan=cmath.polar(t2-t1)\n",
" phitan%=2*pi #counter-clockwise 0-360deg\n",
" arcs=[(l1,phi_rim*phi_spoke2),(r1*(phitan-phi_rim*phi_spoke2),phitan-phi_rim*phi_spoke2),(ltan,0),(r2*(phitan-((1-phi_hub)*phi_spoke2)),-phitan+((1-phi_hub)*phi_spoke2)),(l2,phi_hub*phi_spoke2)]\n",
" return arcs\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Import FullControl, colab.files"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"if 'google.colab' in str(get_ipython()):\n",
" try:\n",
" import fullcontrol as fc\n",
" except Exception as e:\n",
" print(e)\n",
" print('Attempting to install missing packages. Please wait ...')\n",
" !pip install git+https://github.com/FullControlXYZ/fullcontrol --quiet\n",
" import fullcontrol as fc\n",
" from google import colab\n",
"import fullcontrol as fc\n",
"from ipywidgets import widgets\n",
"preview_output=widgets.Output()#shared preview for all models because of problems with plotly custom widget"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### StepGenerator()"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [],
"source": [
"def StepGenerator(PointGenerator,/,*,nominal_ew,hl,nominal_print_speed,max_print_speed,**__):\n",
" old_eh=-1e9\n",
" old_ew=-1e9 \n",
" initialized=False\n",
" for x,y,z,eh,ew,*_ in PointGenerator:\n",
" #update extrusion geometry if it has changed\n",
" if (abs(eh-old_eh)/hl)>0.01 or abs(ew-old_ew)>0.005:\n",
" # print(f'{zb=}{z=}{zt=}{eh=}{w=}')\n",
" if (ew==0) or (eh==0): \n",
" yield fc.Extruder(on=False)\n",
" initialized=False\n",
" else:\n",
" yield fc.ExtrusionGeometry(area_model='rectangle',height=eh,width=ew)\n",
" yield fc.Printer(print_speed=min(max_print_speed,nominal_print_speed*nominal_ew/ew))#set print speed to keep extrusion rate constant\n",
" old_eh=eh\n",
" old_ew=ew\n",
" if not initialized:\n",
" yield from fc.travel_to(fc.Point(x=x,y=y))\n",
" yield from fc.travel_to(fc.Point(z=z))\n",
" yield fc.Extruder(on=True)\n",
" initialized=True\n",
" else:\n",
" yield fc.Point(x=x,y=y,z=z)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## linear mesh"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"jupyter": {
"source_hidden": true
}
},
"outputs": [],
"source": [
"import matplotlib.pyplot as plt\n",
"import matplotlib\n",
"import numpy\n",
"import cmath \n",
" \n",
"%matplotlib notebook\n",
"\n",
"linearData=dict(l1=0.15, r1=0.1, l2=0.15, r2=0.1,strands=5)\n",
"def update_linear_plot(l1,r1,l2,r2,strands,**kwargs):\n",
" output1.clear_output(wait=True)\n",
" with output1:\n",
" %matplotlib inline\n",
" fig=plt.figure(figsize=(3,3)) \n",
" ax=fig.add_subplot(1,1,1) \n",
" plotArcchain(ax,0+0j,1+0j,((l1,0),(r1*2*pi,2*pi)))\n",
" c1=l1+1j*r1\n",
" ax.plot(c1.real,c1.imag,('k+'))\n",
" plotArcchain(ax,1+1j,-1+0j,((l2,0),(r2*2*pi,2*pi)))\n",
" c2=(1-l2)+1j*(1-r2)\n",
" ax.plot(c2.real,c2.imag,'k+') \n",
" t1,t2=calcTangent(c1,r1,c2,r2)\n",
" ax.plot([t1.real,t2.real],[t1.imag,t2.imag]) \n",
" ax.set_aspect('equal')\n",
" ax.set_aspect(1.0)\n",
" display(fig)\n",
" plt.close()\n",
" output2.clear_output(wait=True)\n",
" with output2:\n",
" fig=plt.figure(figsize=(12,3 )) \n",
" ax=fig.add_subplot(1,1,1) \n",
" ltan,phitan=cmath.polar(t2-t1)\n",
" phitan%=2*pi\n",
" arcs=[(l1,0),(r1*phitan,phitan),(ltan,0),(r2*phitan,-phitan),(l2,0)]\n",
" arcs=(arcs+arcs[-1::-1])*2\n",
" for i in range(strands):\n",
" p0=2*i/strands+0j\n",
" n0=1+0j\n",
" plotArcchain(ax,p0,n0,arcs,c=['r','g','b','y','k'][i])\n",
" ax.set_xlim(0,4)\n",
" ax.set_aspect('equal')\n",
" display(fig)\n",
" plt.close()\n",
" output3.clear_output(wait=True)\n",
" with output3:\n",
" print(f'{l1=:0.3f}, {r1=:0.3f}, {l2=:0.3f}, {r2=:0.3f}, {strands=:d}')\n",
" print(f'mesh={arcs[:5]}')\n",
" plt.close()\n",
"\n",
"from ipywidgets import widgets,HBox,VBox\n",
"output1=widgets.Output() \n",
"output2=widgets.Output()\n",
"output3=widgets.Output()\n",
"def handle_linearChange(msg):\n",
" linearData[msg['owner'].description] = msg['new']\n",
" update_linear_plot(**linearData)\n",
"\n",
"widgetList1=dict()\n",
"\n",
"for key,value in linearData.items():\n",
" if type(value)==float:\n",
" widgetList1[key]=widgets.FloatSlider(description=key,\n",
" min=0.0,max=1.0,value=value,step=0.01,continuous_update=False, orientation='horizontal',layout={'width':'5in'},\n",
" readout_format='0.3f')\n",
" else:\n",
" widgetList1[key]=widgets.BoundedIntText(description=key,\n",
" min=1,max=5,value=value,step=1,continuous_update=False,layout={'width':'1.5in'},\n",
" readout_format='d')\n",
" widgetList1[key].observe(handle_linearChange,'value')\n",
"\n",
"Layout1=VBox(\n",
" [\n",
" HBox([VBox([widgets.HTML(value=\"<h2>Parameters</h2>\"),widgetList1['l1'], widgetList1['r1'],widgetList1['l2'],widgetList1['r2'],widgetList1['strands']],), output1,],),\n",
" output2,\n",
" output3\n",
" ])\n",
"update_linear_plot(**linearData);"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## circular mesh"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {
"jupyter": {
"source_hidden": true
}
},
"outputs": [],
"source": [
"from cmath import pi\n",
"circularData=dict(R_rim= 10.000, R_hub=3.500, n_spokes=13, n_strands=5,\n",
" phi_rim=0.160, r_fillet_rim=0.060, phi_hub=0.050, r_fillet_hub=0.200,)\n",
"\n",
"deg=pi/180.0 \n",
"\n",
"def update_circular_plot(*args,R_rim, R_hub, n_spokes, phi_rim, r_fillet_rim, phi_hub, r_fillet_hub, n_strands,**kwargs):#all arguments are required keyword arguments. additional arguments are allowed\n",
" output4.clear_output(wait=True)\n",
" phi_tot=2*pi*n_strands\n",
" phi_spoke2=phi_tot/(2*n_spokes)\n",
" l1,r1,l2,r2=phi_rim*phi_spoke2*R_rim,r_fillet_rim*phi_spoke2*R_rim,phi_hub*phi_spoke2*R_hub,r_fillet_hub*phi_spoke2*R_hub\n",
" with output4:\n",
" %matplotlib inline\n",
" fig=plt.figure(figsize=(4,4)) \n",
" ax=fig.add_subplot(1,1,1) \n",
" if phi_spoke2>60*deg:\n",
" ax.plot(0,0,'+')\n",
" for phi in np.exp(1j*np.linspace(0,phi_spoke2,2)):\n",
" p1=-1.0j*(R_hub-0.2*(R_rim-R_hub))*phi\n",
" p2=-1.0j*(R_rim+0.2*(R_rim-R_hub))*phi\n",
" ax.plot((p1.real,p2.real),(p1.imag,p2.imag),'k-.',lw=1)\n",
" xlim=ax.get_xlim()\n",
" ylim=ax.get_ylim()\n",
" plotArcchain(ax,0-1j*R_rim,1+0j,((l1,phi_rim*phi_spoke2),(r1*2*pi,2*pi)))\n",
" c1=(0-1j*(R_rim-r1))*exp(1j*phi_rim*phi_spoke2)\n",
" ax.plot(c1.real,c1.imag,('k+'))\n",
" plotArcchain(ax,(0-1j*R_hub)*exp(1j*phi_spoke2),(-1+0j)*exp(1j*phi_spoke2),((l2,-phi_hub*phi_spoke2),(r2*2*pi,2*pi)))\n",
" c2=(0-1j*(R_hub+r2))*exp(1j*((1-phi_hub)*phi_spoke2))\n",
" ax.plot(c2.real,c2.imag,'k+') \n",
" t1,t2=calcTangent(c1,r1,c2,r2)\n",
" ax.plot([t1.real,t2.real],[t1.imag,t2.imag]) \n",
" ltan,phitan=cmath.polar(t2-t1)\n",
" phitan%=2*pi #counter-clockwise 0-360deg\n",
" arcs=[(l1,phi_rim*phi_spoke2),(r1*(phitan-phi_rim*phi_spoke2),phitan-phi_rim*phi_spoke2),(ltan,0),(r2*(phitan-((1-phi_hub)*phi_spoke2)),-phitan+((1-phi_hub)*phi_spoke2)),(l2,phi_hub*phi_spoke2)]\n",
" arcs=(arcs+arcs[-1::-1])# add mirrored arc sequence\n",
" for i in range(n_strands):\n",
" dphi=2*phi_spoke2/n_strands\n",
" phi=exp(-1j*dphi*(i+1))\n",
" plotArcchain(ax,(-1j*R_rim)*phi,(1+0j)*phi,arcs*2,c='lightgray',zorder=-1)\n",
" if n_strands<2:\n",
" for r in [R_hub,R_rim]:\n",
" p=-1j*r*np.exp(1j*np.linspace(0,phi_spoke2,25))\n",
" ax.plot(p.real,p.imag,c='lightgray',lw=1,zorder=-1)\n",
" ax.set_xlim(xlim)\n",
" ax.set_ylim(ylim)\n",
" ax.set_aspect('equal')\n",
" ax.set_aspect(1.0)\n",
" display(fig)\n",
" plt.close()\n",
" output5.clear_output(wait=True)\n",
" with output5:\n",
" fig=plt.figure(figsize=(6,6)) \n",
" ax=fig.add_subplot(1,1,1) \n",
" p0=0-1j*R_rim\n",
" n0=1+0j\n",
" plotArcchain(ax,p0,n0,arcs*n_spokes,c='b')\n",
" ax.plot([xlim[0],xlim[1],xlim[1],xlim[0],xlim[0]],[ylim[0],ylim[0],ylim[1],ylim[1],ylim[0]],'--',c='gray',lw=1)\n",
" ax.set_aspect('equal')\n",
" display(fig)\n",
" plt.close()\n",
" output6.clear_output(wait=True)\n",
" with output6:\n",
" from math import gcd\n",
" if gcd(n_strands,n_spokes)!=1:\n",
" print('\\x1b[31m'+f'n_strands and n_spokes are not coprime: {gcd(n_strands,n_spokes)=} (should be 1)'+'\\x1b[0m')\n",
" print(f'{R_rim=: 0.3f}, {R_hub=:0.3f}, {n_spokes=:d}, {n_strands=:d},')\n",
" print(f'{phi_rim=:0.3f}, {r_fillet_rim=:0.3f}, {phi_hub=:0.3f}, {r_fillet_hub=:0.3f},')\n",
" print()\n",
" print(f'mesh={arcs[:5]}')\n",
" \n",
" plt.close()\n",
"\n",
"from ipywidgets import widgets,HBox,VBox\n",
"output4=widgets.Output() \n",
"output5=widgets.Output()\n",
"output6=widgets.Output()\n",
"\n",
"def handle_circularChange(msg):\n",
" circularData[msg['owner'].description] = msg['new']\n",
" update_circular_plot(**circularData)\n",
" \n",
"widgetList2={}\n",
"for key,value in circularData.items():\n",
" if type(value)==int:\n",
" widgetList2[key]=widgets.IntText(description=key, value=value,layout={'width':'1.5in'}, readout_format='d')\n",
" elif type(value)==float:\n",
" widgetList2[key]=widgets.FloatText(description=key, value=value,layout={'width':'1.5in'}, readout_format='0.3f')\n",
" else: print(f'need to add widget for {key=}, {type(value)=}');continue\n",
" widgetList2[key].observe(handle_circularChange,'value')\n",
"\n",
"Layout2=VBox(\n",
" [HBox([\n",
" VBox([HBox([VBox([widgetList2['R_rim'],widgetList2['R_hub'],widgetList2['n_spokes'],widgetList2['n_strands']],),VBox([widgetList2['phi_rim'], widgetList2['r_fillet_rim'],widgetList2['phi_hub'],widgetList2['r_fillet_hub'],]),],),\n",
" output4]),\n",
" output5,]),\n",
" output6\n",
" ])\n",
"update_circular_plot(**circularData);\n",
"plt.close()"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {
"jupyter": {
"source_hidden": true
}
},
"outputs": [],
"source": [
"#transformer, rotor,...\n",
"def mesh_transformer(*,R_rim,R_hub,outline,p0o,n0o,outline_stretch=None,inline,p0i,n0i,inline_stretch=None,finline_offset=lambda *_:0.0,foutline_offset=lambda *_:0.0,**kwargs):\n",
" import cmath\n",
" unstretched_outline=cumsum([l for l,*_ in outline],0.0)\n",
" unstretched_outline=[x/unstretched_outline[-1] for x in unstretched_outline]\n",
" outlineInterpolator_=arcChainInterpolator(arcChain=outline,p0=p0o,a0=n0o)\n",
" if outline_stretch!=None:\n",
" stretched_outline=cumsum([l/s for (l,*_),s in zip(outline,outline_stretch)],0.0)\n",
" stretched_outline=[x/stretched_outline[-1] for x in stretched_outline]\n",
" outlineInterpolator=lambda T_mesh:outlineInterpolator_(interp(T_mesh,stretched_outline,unstretched_outline))\n",
" else:\n",
" outlineInterpolator=outlineInterpolator_\n",
" unstretched_inline=cumsum([l for l,*_ in inline],0.0)\n",
" unstretched_inline=[x/unstretched_inline[-1] for x in unstretched_inline]\n",
" inlineInterpolator_=arcChainInterpolator(arcChain=inline,p0=p0i,a0=n0i)\n",
" if inline_stretch!=None:\n",
" stretched_inline=cumsum([l/s for (l,*_),s in zip(inline,inline_stretch)],0.0)\n",
" stretched_inline=[x/stretched_inline[-1] for x in stretched_inline]\n",
" inlineInterpolator=lambda T_mesh:inlineInterpolator_(interp(T_mesh,stretched_inline,unstretched_inline))\n",
" else:\n",
" inlineInterpolator=inlineInterpolator_\n",
" def transform_mesh(pz,/):\n",
" p=pz[0]\n",
" #transform raw mesh to tripod shape: \n",
" T_mesh=(cmath.polar(p)[1]/(2*pi))%1.0 #phase angle of mesh points [0..1[\n",
" r_mesh=abs(p)#amplitude of mesh points\n",
" pout,aout,Lout,*_=outlineInterpolator(T_mesh)\n",
" pout+=aout*1j*foutline_offset(pz[1],Lout)\n",
" pin,ain,Lin,*_=inlineInterpolator(T_mesh)\n",
" pin+=ain*1j*finline_offset(pz[1],Lin)\n",
" p_transformed_point=pin+(pout-pin)*((r_mesh-R_hub)/(R_rim-R_hub)) #transform raw mesh to tripod shape\n",
" pz[0]=p_transformed_point\n",
" return pz\n",
" return transform_mesh\n",
" \n",
"def calibrator(*,p_center=0.0+0.0j,r_ref,dr_offset=0.0,f_offset=None,dr_rigid=0.0,mesh_compression=2.0,ew_factor=None,**kwargs):\n",
" import cmath\n",
" r_rigid_max=r_ref+dr_rigid\n",
" r_rigid_min=r_ref-dr_rigid\n",
" def calibrate(p_z_ew,/):\n",
" p=p_z_ew[0]\n",
" dr=dr_offset\n",
" if ew_factor!=None:\n",
" dr+=p_z_ew[2]*ew_factor\n",
" dp=p-p_center\n",
" r=abs(dp)\n",
" if f_offset==None:\n",
" r_max=r_rigid_max+dr/2+abs(dr)*(0.5+1/(mesh_compression-1))\n",
" r_min=r_rigid_min+dr/2-abs(dr)*(0.5+1/(mesh_compression-1))\n",
" if (r>r_max) or (r<r_min): \n",
" return p_z_ew#return early if point is outside the affected zone\n",
" phi=dp/r\n",
" if f_offset!=None:\n",
" dr+=f_offset(p_z_ew[1],(cmath.polar(phi)[1]/(2*pi))%1,r=r_ref)\n",
" r_max=r_rigid_max+dr/2+abs(dr)*(0.5+1/(mesh_compression-1))\n",
" r_min=r_rigid_min+dr/2-abs(dr)*(0.5+1/(mesh_compression-1))\n",
" if (r>r_max) or (r<r_min): \n",
" return p_z_ew#return early if point is outside the affected zone\n",
" if r_rigid_min < r <r_rigid_max: #just shift the point if it is in the rigid zone\n",
" p_z_ew[0]+=dr*phi\n",
" return p_z_ew\n",
" if r>r_ref:#scale the amout of shift down to zero towards r_max\n",
" if (r_max-r_rigid_max)!=0.0: \n",
" x=(r_max-r)/(r_max-r_rigid_max)\n",
" p_z_ew[0]=p_center+(r_max - x*(r_max - (dr+r_rigid_max)))*phi\n",
" return p_z_ew\n",
" else: #(r<=r_ref) scale the amount of shift down to zero towards r_min\n",
" if (r_rigid_min-r_min)!=0.0: \n",
" x=(r-r_min)/(r_rigid_min-r_min)\n",
" p_z_ew[0]=p_center+(r_min + x*((dr+r_rigid_min)-r_min))*phi\n",
" return p_z_ew\n",
" return calibrate\n",
"\n",
" \n",
"def rotor(*,center=0+0j,angle=None,phase=None,fphase=None,**kwargs):\n",
" if fphase != None:\n",
" def rotate(pz,/):\n",
" pz[0]=center+(pz[0]-center)*fphase(pz[1],pz)\n",
" return pz\n",
" else: \n",
" if angle!=None:\n",
" phase=1j**(angle*2/pi)#same as cmath.exp(1j*angle), but without cmath\n",
" if phase == None:\n",
" raise Exception(\"Argunent error in call to 'rotor': one of the arguments {phase|angle|fphase} is required\") \n",
" def rotate(pz,/):\n",
" pz[0]=center+(pz[0]-center)*phase\n",
" return pz\n",
" return rotate\n",
" \n",
"def terminator(stopcondition=lambda *_:False):\n",
" def checkterm(p,/):\n",
" for x in p:\n",
" if stopcondition(x):\n",
" return\n",
" else:\n",
" yield x\n",
" return\n",
" def terminate(p,/):\n",
" if hasattr(p,'__next__'):\n",
" return checkterm(p)\n",
" else:\n",
" return p\n",
" return terminate\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Tripod "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Tripod()"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {
"jupyter": {
"source_hidden": true
}
},
"outputs": [],
"source": [
"#Tripod\n",
"from cmath import pi\n",
"import cmath\n",
"import math\n",
"from functools import partial\n",
"tripod_parameters=dict(R_rim= 35.000, R_hub=31.000, n_spokes=63, n_strands=5,\n",
" phi_rim=0.160, r_fillet_rim=0.060, phi_hub=0.160, r_fillet_hub=0.060,\n",
" ew_rim=1.0,ew_fillet_rim=0.8,ew_spokes=0.7,ew_fillet_hub=0.5,ew_hub=0.5,\n",
" L=35.000, R1=11.000, R2=4.000, R3=8.000, R4=8.000, R5=26.000,\n",
" sL=1.000, sR1=1.500, sR2=0.450, sR3=1.000, sR4=0.700, sR5=0.800,\n",
" z_=0.4,H=8,w_chamfer=1.0,w_chamfer1=6.5,w_chamfer1_bottom=0.3,R1_tolerance=0.2,ew_factor=0.5,\n",
" n_skirt=3,skirt_offset=1.0,R2_tolerance=0.25,hl=0.2,hl_start=0.05,\n",
" #print_parameters\n",
" design_name = 'Tripod',\n",
" nozzle_temp = 220.0, bed_temp = 120.0,\n",
" nominal_print_speed = 20.0*60.0,#10*60 #print slow to give the layer time to cool\n",
" max_print_speed = 30*60,#=speed for ew=0.5mm \n",
" nominal_ew = 0.75, # extrusion width\n",
" fan_percent = 0.0,\n",
" # nominal_eh = 0.2, # extrusion/layer heigth\n",
" printer_name='generic', # generic / ultimaker2plus / prusa_i3 / ender_3 / cr_10 / bambulab_x1 / toolchanger_T0\n",
" )\n",
"\n",
"tripod_parameter_descriptions={'nominal_print_speed':'print speed',\n",
" 'nominal_ew': 'extrusion width',\n",
" 'hl':'layer height',\n",
" }\n",
"\n",
"\n",
"\n",
"deg=pi/180.0 \n",
"def tripod( z_=None,*,#keywords only from here on\n",
" R_rim= 35.000, R_hub=31.000, n_spokes=63, n_strands=5,\n",
" phi_rim=0.160, r_fillet_rim=0.060, phi_hub=0.160, r_fillet_hub=0.060,\n",
" ew_rim=1.0,ew_fillet_rim=0.8,ew_spokes=0.7,ew_fillet_hub=0.5,ew_hub=0.5,\n",
" \n",
" L=35.000, R1=11.000, R2=4.000, R3=8.000, R4=8.000, R5=26.000,\n",
" sL=1.000, sR1=1.500, sR2=0.450, sR3=1.000, sR4=0.700, sR5=0.800,\n",
" H=8,w_chamfer=1.0,w_chamfer1_bottom=0.3,w_chamfer1=6.5,R1_tolerance=0.2,ew_factor=0.5,\n",
" n_skirt=0,skirt_offset=1.0,R2_tolerance=0.25,hl=0.2,hl_start=0.05,\n",
" env=None,**kwargs):\n",
" import math\n",
" from math import pi\n",
" deg=pi/180\n",
" #geometry of the mesh:\n",
" phi_tot=2*pi*n_strands\n",
" phi_spoke2=phi_tot/(2*n_spokes)#angle be\n",
" l1,r1,l2,r2=phi_rim*phi_spoke2*R_rim,r_fillet_rim*phi_spoke2*R_rim,phi_hub*phi_spoke2*R_hub,r_fillet_hub*phi_spoke2*R_hub\n",
" c1=(0-1j*(R_rim-r1))*cmath.exp(1j*phi_rim*phi_spoke2)#center of the rim fillet\n",
" c2=(0-1j*(R_hub+r2))*cmath.exp(1j*((1-phi_hub)*phi_spoke2))#center of the hub fillet\n",
" t1,t2=calcTangent(c1,r1,c2,r2)#end points of tangent between fillet1 and fillet 2\n",
" ltan,phitan=cmath.polar(t2-t1)\n",
" phitan%=2*pi #counter-clockwise 0-360deg\n",
" arcs=[(l1,phi_rim*phi_spoke2),(r1*(phitan-phi_rim*phi_spoke2),phitan-phi_rim*phi_spoke2),(ltan,0),(r2*(phitan-((1-phi_hub)*phi_spoke2)),-phitan+((1-phi_hub)*phi_spoke2)),(l2,phi_hub*phi_spoke2)]\n",
" l_layer=sum(l for l,*_ in arcs)*2*n_spokes #extrusion path length for one complete layer (used to calculate z-coordinate)\n",
" arcs=(arcs+arcs[-1::-1])# add mirrored arc sequence\n",
"\n",
" #lookup table for the extrusion widths:\n",
" arc_ew=[ew_rim,ew_fillet_rim,ew_spokes,ew_fillet_hub,ew_hub]#extrusion widths for rim...hub arc segments\n",
" arc_ew=arc_ew+arc_ew[-1::-1]#mirror the 5 segments\n",
" \n",
" #outer surface of the deformed mesh:\n",
" p0o=L+R3+0j\n",
" n0o=0+1j\n",
" def sss(c,a,b): #triangle with 3 sides: return angle opposite first side 'c'\n",
" cosgamma=(a**2+b**2-c**2)/(2*a*b)\n",
" return math.acos(cosgamma)\n",
" phi3=180*deg-sss(R5+R4,L,R3+R4) #angle for R3\n",
" phi4=sss(L,R5+R4,R3+R4) #angle for R4\n",
" phi5=60*deg-sss(R3+R4,L,R5+R4) #angle for R5\n",
" outline=[(R3*phi3,phi3),(R4*phi4,-phi4),(R5*phi5,phi5)]#turtle path for 1/6 of the outline\n",
" outline+=outline[-1::-1]#mirror to get 1/3 of the outline\n",
" outline*=3 #repeat 3 times to get the complete closed outline turtle path\n",
" \n",
" #inner surface of the deformed mesh:\n",
" p0i=L+R2+0j\n",
" n0i=0+1j\n",
" inline=[(R2*180*deg,180*deg),(0,-90*deg),(L-R2-R1,0),(0,-90*deg),(R1*60*deg,60*deg)]# 1/6 of inside path\n",
" inline+=inline[-1::-1]# add mirrored path\n",
" inline*=3 #repeat 3 times\n",
" \n",
" #stretch factors for inline/outline:\n",
" outline_stretch=[sR3,sR4,sR5]\n",
" outline_stretch+=outline_stretch[-1::-1]\n",
" outline_stretch*=3\n",
" inline_stretch=[sR2,1,sL,1,sR1]#there are 2 90° turns in the path which cannot be stretched\n",
" inline_stretch+=inline_stretch[-1::-1]\n",
" inline_stretch*=3\n",
"#1. generate the points of the raw annular mesh:\n",
" blank_points=lambda:Segments2Complex(arcs,p0=R_rim+0.j,a0=0+1j,tol=0.005,return_start=True,loops=n_spokes if z_!=None else math.inf)\n",
" point_data=NamedList(p=None,z=None,ew=None,a=None)\n",
" stopcondition=lambda p_z_ew: p_z_ew[1] > (H+hl) \n",
" meshpoints=lambda:pipe(\n",
" #2. calculate the z-coordinate based on the total extrusion length, re-arrange the variables:\n",
"# iterize(lambda palx:list((palx[0],z_ if z_!=None else hl*palx[2]/l_layer+hl_start,arc_ew[palx[3]],palx[1],))), \n",
" iterize(lambda palx:point_data.update(p=palx[0],z=z_ if z_!=None else hl*palx[2]/l_layer+hl_start,ew=arc_ew[palx[3]],a=palx[1],)), \n",
" #3. check if the top (z=h+hl) is reached, and end the iteration if this is the case:\n",
" terminator(stopcondition=stopcondition), \n",
" #4. rotate the mesh (helical spokes):\n",
" iterize(rotor(fphase=lambda z,*_:1j**(z/H/n_spokes*4))),\n",
" #5. deform the annular mesh to fit between 'inline' and 'outline', and top/bottom chamfer the outside outline:\n",
" iterize(mesh_transformer(R_rim=R_rim,R_hub=R_hub, \n",
" outline=outline,p0o=p0o,n0o=n0o,outline_stretch=outline_stretch,\n",
" foutline_offset=lambda z,*_,**__:max(0,w_chamfer-z,w_chamfer-(H-z)),\n",
" inline=inline,p0i=p0i,n0i=n0i,inline_stretch=inline_stretch,)),\n",
" #6. counter-sink the 3 'R2' holes:\n",
" *(iterize(calibrator(p_center=L*1j**(i/3*4),r_ref=R2,f_offset=lambda z,*_,**__:max(0,(w_chamfer-z),w_chamfer-(H-z))))for i in range(3)),\n",
" #7. tap the 3 'R2' holes (M8 ISO thread), apply tolerance and correction for extrusion with:\n",
" *(iterize(calibrator(p_center=L*1j**(i/3*4),r_ref=R2,f_offset= ISO_thread(Pitch=1.25), dr_offset=R2_tolerance, ew_factor=ew_factor))for i in range(3)),\n",
" #8. chamfer the center hole:\n",
" iterize(calibrator(r_ref=R1,f_offset=lambda z,*_,**__:max(R1_tolerance,w_chamfer1_bottom-z,w_chamfer1-1.0*(H-z)),ew_factor=ew_factor)),\n",
" #9. convert the complex 2D coordinate to real x, y coordinates, calculate eh, return (x,y,z,eh,ew):\n",
" iterize(lambda p_z_ew:(p_z_ew[0].real,p_z_ew[0].imag,min(p_z_ew[1],H),min(p_z_ew[1] if p_z_ew[1]<(hl+hl_start) else hl ,H-(p_z_ew[1]-hl)),p_z_ew[2],)),\n",
" )(blank_points())\n",
" def skirt_and_meshpoints():\n",
" skirt=((p[0].real,p[0].imag,hl,hl,0.6) for offs in range(n_skirt) for p in Segments2Complex(outline,p0=L+R3+0.0j,a0=0+1j,tol=0.005,offs=0.5*offs+1,return_start=True) )\n",
" for x,y,z,eh,ew in skirt:#skip the first few points so that the start point of the skirt is not near the start point of the print.\n",
" if y>R3:\n",
" break\n",
" yield from skirt\n",
" yield L+R3,0,hl,0.0,0.0 #move to start of print\n",
" yield from meshpoints()\n",
" if z_==None:\n",
" yield 0.0,0.0,max(H+10,30),0.0,0.0 #move print head go to parking position if not layer preview\n",
" if env!=None:\n",
" env.update({key:value for key,value in locals().items() if not key in ['env','args','kwargs']})\n",
" return skirt_and_meshpoints if ((z_==None) and(n_skirt>0)) or ((z_<=hl) and (n_skirt>0)) else meshpoints\n",
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### update_tripod_plot()"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [],
"source": [
"def update_tripod_plot(*,\n",
" z_=None, R_rim, R_hub, n_spokes, n_strands, phi_rim, r_fillet_rim, phi_hub, r_fillet_hub, \n",
" ew_rim, ew_fillet_rim, ew_spokes, ew_fillet_hub, ew_hub, L, R1, R2, R3, R4, R5, \n",
" sL, sR1, sR2, sR3, sR4, sR5, H, w_chamfer, w_chamfer1_bottom, w_chamfer1, R1_tolerance, \n",
" ew_factor, n_skirt, skirt_offset, R2_tolerance, hl, hl_start, pi, deg, p0o, n0o, sss, \n",
" phi3, phi4, phi5, p0i, n0i, inline, outline_stretch, inline_stretch, skirt_and_meshpoints, \n",
" arc_ew, arcs, blank_points, l_layer, math, meshpoints, outline, point_data, stopcondition, \n",
" \n",
" phi_spoke2,l1,r1,l2,r2,c1,c2,t1,t2,\n",
" **kwargs ):\n",
"\n",
" output7.clear_output(wait=True)\n",
" phi_tot=2*pi*n_strands\n",
"# phi_spoke2=phi_tot/(2*n_spokes)\n",
"# l1,r1,l2,r2=phi_rim*phi_spoke2*R_rim,r_fillet_rim*phi_spoke2*R_rim,phi_hub*phi_spoke2*R_hub,r_fillet_hub*phi_spoke2*R_hub\n",
" with output7:\n",
" %matplotlib inline\n",
" fig=plt.figure(figsize=(3.5,3.5)) \n",
" ax=fig.add_subplot(1,1,1) \n",
" if phi_spoke2>60*deg:\n",
" ax.plot(0,0,'+')\n",
" for phi in np.exp(1j*np.linspace(0,phi_spoke2,2)):\n",
" p1=-1.0j*(R_hub-0.2*(R_rim-R_hub))*phi\n",
" p2=-1.0j*(R_rim+0.2*(R_rim-R_hub))*phi\n",
" ax.plot((p1.real,p2.real),(p1.imag,p2.imag),'k-.',lw=1)\n",
" xlim=ax.get_xlim()\n",
" ylim=ax.get_ylim()\n",
" plotArcchain(ax,0-1j*R_rim,1+0j,((l1,phi_rim*phi_spoke2),(r1*2*pi,2*pi)))\n",
"# c1=(0-1j*(R_rim-r1))*exp(1j*phi_rim*phi_spoke2)\n",
" ax.plot(c1.real,c1.imag,('k+'))\n",
" plotArcchain(ax,(0-1j*R_hub)*exp(1j*phi_spoke2),(-1+0j)*exp(1j*phi_spoke2),((l2,-phi_hub*phi_spoke2),(r2*2*pi,2*pi)))\n",
"# c2=(0-1j*(R_hub+r2))*exp(1j*((1-phi_hub)*phi_spoke2))\n",
" ax.plot(c2.real,c2.imag,'k+') \n",
"# t1,t2=calcTangent(c1,r1,c2,r2)\n",
" ax.plot([t1.real,t2.real],[t1.imag,t2.imag]) \n",
" ltan,phitan=cmath.polar(t2-t1)\n",
" phitan%=2*pi #counter-clockwise 0-360deg\n",
" arcs=[(l1,phi_rim*phi_spoke2),(r1*(phitan-phi_rim*phi_spoke2),phitan-phi_rim*phi_spoke2),(ltan,0),(r2*(phitan-((1-phi_hub)*phi_spoke2)),-phitan+((1-phi_hub)*phi_spoke2)),(l2,phi_hub*phi_spoke2)]\n",
" arcs=(arcs+arcs[-1::-1])# add mirrored arc sequence\n",
" for i in range(n_strands):\n",
" dphi=2*phi_spoke2/n_strands\n",
" phi=exp(-1j*dphi*(i+1))\n",
" plotArcchain(ax,(-1j*R_rim)*phi,(1+0j)*phi,arcs*2,c='lightgray',zorder=-1)\n",
" if n_strands<2:\n",
" for r in [R_hub,R_rim]:\n",
" p=-1j*r*np.exp(1j*np.linspace(0,phi_spoke2,25))\n",
" ax.plot(p.real,p.imag,c='lightgray',lw=1,zorder=-1)\n",
" ax.set_xlim(xlim)\n",
" ax.set_ylim(ylim)\n",
" ax.set_aspect('equal')\n",
" ax.set_aspect(1.0)\n",
" display(fig)\n",
" plt.close()\n",
" output8.clear_output(wait=True)\n",
" with output8:\n",
" fig=plt.figure(figsize=(6,6)) \n",
" ax=fig.add_subplot(1,1,1) \n",
" plotArcchain(ax,p0o,n0o,outline,'b',lw=2,zorder=2)\n",
" plotArcchain(ax,p0i,n0i,inline,'b',lw=2,zorder=2)\n",
" ax.plot(0,0,'b',lw=2,label='mesh outline')\n",
" p_arrow=R1*cmath.exp(1j*45*deg)\n",
" for text,c,r,offs,ang in [(r'$R_1$',0+0j,R1,-7.5,150*deg),(r'$R_2$',L*cmath.exp(1j*0*deg),R2,-9.5,45*deg),(r'$R_3$',L+0j,R3,8.0,60*deg),\n",
" (r'$R_4$',L+(R3+R4)*cmath.exp(1j*phi3),-R4,8.0,60*deg),(r'$R_5$',0+0j,R5,8.0,50*deg),\n",
" (r'$L$',L/2-10j,L/2,-L/2,0*deg),(r'$L$',L/2-10j,L/2,-L/2,180*deg)]:\n",
" phi=cmath.exp(1j*ang)\n",
" ptip=c+r*phi \n",
" ptext=c+(r+offs)*phi\n",
" ax.annotate(text,xy=(ptip.real,ptip.imag),xycoords='data',\n",
" xytext=(ptext.real, ptext.imag), textcoords='data',arrowprops=dict(facecolor='black', headwidth=6, headlength=10, width=0.25, shrink=0.0),\n",
" horizontalalignment='center', verticalalignment='center',fontsize=14,backgroundcolor='w')\n",
" for p1,p2 in [(0+5j,0-15j),(-5,5),(L+5j,L-15j),(L-5,L+5)]:\n",
" ax.plot((p1.real,p2.real),(p1.imag,p2.imag),'k-.',lw=1) \n",
" p_mesh=list(zip(*((p.real,p.imag) for p,*_ in Segments2Complex(arcs,p0=0.0-1j*R_rim,a0=1.0+0.0j,tol=0.01,return_start=True,loops=n_spokes))))\n",
" ax.plot(p_mesh[0],p_mesh[1],'-',c='lightgray',label='raw mesh',zorder=-1) \n",
" ax.plot([xlim[0],xlim[1],xlim[1],xlim[0],xlim[0]],[ylim[0],ylim[0],ylim[1],ylim[1],ylim[0]],'--',c='gray',lw=1,label='mesh detail') \n",
" transformed_mesh_points=list(zip(*meshpoints()))\n",
" ax.plot(transformed_mesh_points[0],transformed_mesh_points[1],'-',c='g',label='transformed mesh',zorder=1)\n",
" if (n_skirt>0) and (z_<=hl):\n",
" skirt=list(zip(*((p[0].real,p[0].imag) for offs in range(n_skirt) for p in Segments2Complex(outline,p0=L+R3+0.0j,a0=0+1j,tol=0.005,offs=0.5*offs+1,return_start=True) )))\n",
" ax.plot(skirt[0],skirt[1],'c-',label='skirt')\n",
"\n",
"# NEMA 17 outline\n",
" A,B,C,D,E,F=42.2,22,31,4,3,5\n",
" p0=A/2+0j\n",
" n0=0+1j\n",
" NEMA=[(A/2-D,0),(D*45*deg,45*deg)]\n",
" plotArcchain(ax,p0,n0,(NEMA+NEMA[-1::-1])*4,'--',c='gray',zorder=-1)\n",
" ax.plot(0,0,'--',c='gray',label='NEMA 17 motor')\n",
" n0=0+1j\n",
" for p0,r in [(F/2+0j,F/2),(B/2+0j,B/2)]+[(C/2*(1+1j)*1j**i+E/2,E/2) for i in range(4)]:\n",
" plotArc(ax,p0,n0,r*360*deg,360*deg,'--',c='gray',zorder=-1)\n",
" ax.legend(loc='lower right')\n",
" ax.set_aspect('equal')\n",
" display(fig)\n",
" plt.close()\n",
" output9.clear_output(wait=True)\n",
" with output9:\n",
"# print(f'{R3+R4=}, {L=}, {R5+R4=}, {sss(R3+R3,L,R5+R4)/deg=}, {phi3/deg=},{phi4/deg=}, {phi5/deg=}, {R1=}')\n",
"# print(f'{len(skirt)=}, {min(skirt[0])=:.2f}, {max(skirt[0])=:.2f}')\n",
"# print(f'{transformed_mesh_points[0][0]=},{transformed_mesh_points[1][0]=}')\n",
" from math import gcd\n",
" if gcd(n_strands,n_spokes)!=1:\n",
" print('\\x1b[31m'+f'n_strands and n_spokes are not coprime: {gcd(n_strands,n_spokes)=} (should be 1)'+'\\x1b[0m')\n",
" print(f'{R_rim=: 0.3f}, {R_hub=:0.3f}, {n_spokes=:d}, {n_strands=:d},')\n",
" print(f'{phi_rim=:0.3f}, {r_fillet_rim=:0.3f}, {phi_hub=:0.3f}, {r_fillet_hub=:0.3f},')\n",
" print(f'{L=:0.3f}, {R1=:0.3f}, {R2=:0.3f}, {R3=:0.3f}, {R4=:0.3f}, {R5=:0.3f},')\n",
" print(f'{sL=:0.3f}, {sR1=:0.3f}, {sR2=:0.3f}, {sR3=:0.3f}, {sR4=:0.3f}, {sR5=:0.3f},')\n",
" print()\n",
" print(f'mesh={arcs[:5]}')\n",
" print()\n",
" \n",
" plt.close()\n",
"\n",
"from ipywidgets import widgets,HBox,VBox\n",
"output7=widgets.Output() \n",
"output8=widgets.Output()\n",
"output9=widgets.Output()\n",
"\n",
"\n",
"def handle_change_tripod(msg):\n",
" key,value=msg['owner'].key,msg['new']\n",
" tripod_parameters[key] = value\n",
" if key=='H':\n",
" widgetList3['z_'].max=value\n",
" env={}\n",
" tripod(**tripod_parameters,env=env)\n",
" preview_output.clear_output(wait=False) #preview may not be valid, since parameters have changed\n",
" update_tripod_plot(**env)\n",
" \n",
"def save_tripod_gcode(*args,**kwargs):\n",
" from datetime import datetime\n",
" filename=tripod_parameters['design_name']+datetime.now().strftime(\"__%d-%m-%Y__%H-%M-%S\")\n",
" tripod_status_widget.value=\" calculating extrusion path ...\"\n",
" myTripod=tripod(**(tripod_parameters|dict(z_=None))) \n",
"# myTripodPoints=list(myTripod())\n",
" steps=list(StepGenerator(myTripod(),**tripod_parameters))\n",
" xmax,ymax=xmin,ymin=0,0\n",
" for x,y in ((point.x,point.y) for point in steps if type(point)==fc.Point):\n",
" if x!= None: xmin=min(xmin,x)\n",
" if x!= None: xmax=max(xmax,x)\n",
" if y!= None: ymin=min(ymin,y)\n",
" if y!= None: ymax=max(ymax,y)\n",
" \n",
" model_offset = fc.Vector(x=100-0.5*(xmax+xmin), y=100-0.5*(ymax+ymin), z=0.0)\n",
" steps = fc.move(steps, model_offset)\n",
" gcode_controls = fc.GcodeControls(\n",
" printer_name=tripod_parameters['printer_name'],\n",
" save_as=filename,\n",
" include_date=False,\n",
" initialization_data={\n",
" 'primer': 'no_primer',\n",
" }|tripod_parameters\n",
" )\n",
" tripod_status_widget.value=' saving gcode to \"'+filename+'.gcode\" ...'\n",
" fc.transform(steps, 'gcode', gcode_controls)\n",
" if \"colab\" in globals():\n",
" tripod_status_widget.value=' preparing file for download ...'\n",
" colab.files.download(filename+\".gcode\")\n",
" tripod_status_widget.value=''\n",
" \n",
"def update_tripod_preview(*args,**kwargs):\n",
" import os\n",
" preview_output.clear_output(wait=True)\n",
" with preview_output:\n",
" tripod_status_widget.value=\" calculating extrusion path ...\"\n",
" myTripod=tripod(**(tripod_parameters|dict(z_=None))) \n",
" # myTripodPoints=list(myTripod())\n",
" steps=list(StepGenerator(myTripod(),**tripod_parameters))\n",
" xmax,ymax=xmin,ymin=0,0\n",
" for x,y in ((point.x,point.y) for point in steps if type(point)==fc.Point):\n",
" if x!= None: xmin=min(xmin,x)\n",
" if x!= None: xmax=max(xmax,x)\n",
" if y!= None: ymin=min(ymin,y)\n",
" if y!= None: ymax=max(ymax,y)\n",
" model_offset = fc.Vector(x=100-0.5*(xmax+xmin), y=100-0.5*(ymax+ymin), z=0.0)\n",
" steps = fc.move(steps, model_offset)\n",
" style='tube'\n",
" tripod_status_widget.value=' running fc.transform ...'\n",
" if ('iPad' in os.uname().machine):\n",
" plt.close()\n",
" fc.transform(steps, 'plot', fc.PlotControls(style='line',color_type='print_sequence'))\n",
" _=plt.show()\n",
" else:\n",
" _=fc.transform(steps, 'plot', fc.PlotControls(style=style ,color_type='print_sequence'))\n",
" tripod_status_widget.value=''\n",
" \n",
"style={'description_width':'1.0in'}\n",
"layout={'width':'1.7in'}\n",
"widgetList3={}\n",
"for key,value in tripod_parameters.items():\n",
" if type(value)==int:\n",
" widgetList3[key]=widgets.IntText(description=key, value=value,style=style,layout=layout, readout_format='d')\n",
" elif type(value)==float:\n",
" widgetList3[key]=widgets.FloatText(description=tripod_parameter_descriptions.get(key,key), value=value,style=style,layout=layout, readout_format='0.3f')\n",
" elif type(value)==str:\n",
" widgetList3[key]=widgets.Text(description=key, value=value,style=style,layout=layout,)\n",
" elif type(value)==bool:\n",
" widgetList3[key]=widgets.Dropdown(description=key,options=[True,False], value=value,style=style,layout=layout,indent=True)\n",
" else: print(f'need to add widget for {key=}, {type(value)=}');continue\n",
" \n",
"key='z_'\n",
"widgetList3[key]=widgets.FloatSlider(description=key,\n",
" min=0.0,max=tripod_parameters['H'],value=tripod_parameters[key],step=0.2,continuous_update=True, orientation='vertical', \n",
" readout_format='0.1f',layout={'height':'4.5in'})\n",
"for key in tripod_parameters: \n",
" widgetList3[key].observe(handle_change_tripod,'value') \n",
" widgetList3[key].key=key\n",
"\n",
"SaveTripodGcodeButton=widgets.Button(description='download G-Code',on_click=save_tripod_gcode)\n",
"SaveTripodGcodeButton.on_click(save_tripod_gcode)\n",
"UpdateTripodPreviewButton=widgets.Button(description='update preview',on_click=update_tripod_preview)\n",
"UpdateTripodPreviewButton.on_click(update_tripod_preview)\n",
"tripod_status_widget=widgets.Label()\n",
"Layout3=widgets.Tab([VBox(\n",
" [\n",
" HBox([\n",
" VBox([\n",
" widgets.HTML(value='<h2 align=left>Mesh Parameters: </h2>'),\n",
" HBox([widgetList3['n_spokes'],widgetList3['n_strands']]),\n",
" HBox([widgetList3['R_hub'],widgetList3['phi_hub'],widgetList3['r_fillet_hub'],],),\n",
" HBox([widgetList3['R_rim'],widgetList3['phi_rim'],widgetList3['r_fillet_rim'],]),\n",
" ]),\n",
" output7,\n",
" ],layout={'border': '1px solid black'}), \n",
" HBox([ \n",
" VBox([\n",
" widgets.HTML(value='<h2 align=left>Geometry: </h2>'),\n",
" HBox([widgets.HTML(value='<h4 align=center>Dimensions: </h4>',layout=dict(width='50%')),widgets.HTML(value='<h4 align=center>Mesh-Stretch: </h3>',layout=dict(width='50%')), ],layout=dict(width='100%')),\n",
" HBox([widgetList3['L'], widgetList3['sL'], ]),\n",
" HBox([widgetList3['R1'],widgetList3['sR1'],]),\n",
" HBox([widgetList3['R2'],widgetList3['sR2'],]),\n",
" HBox([widgetList3['R3'],widgetList3['sR3'],]),\n",
" HBox([widgetList3['R4'],widgetList3['sR4'],]),\n",
" HBox([widgetList3['R5'],widgetList3['sR5'],]),\n",
" HBox([]),\n",
" HBox([widgetList3['H'],widgetList3['R2_tolerance'],]),\n",
" HBox([widgetList3['w_chamfer'],widgetList3['w_chamfer1'],]),\n",
" HBox([widgetList3['n_skirt'],widgetList3 ['skirt_offset'],]),\n",
" ]),\n",
" HBox([output8,widgetList3['z_']]), \n",
" ],layout={'border': '1px solid black'}),\n",
" VBox([\n",
" # widgets.HTML(value='<h2 align=left>Output: </h2>'), #Title for output box\n",
" output9,\n",
" ],layout={'border': '1px solid black'})\n",
" ]),\n",
" HBox([ \n",
" VBox([\n",
" widgets.HTML(value='<h2 align=left>Print Parameters: </h2>'),\n",
" HBox([widgetList3['hl'],UpdateTripodPreviewButton,tripod_status_widget],)\n",
" ]),\n",
" # tripod_preview_output, #plotly output not showing inside HBox\n",
" ],layout={'border': '1px solid black'}),\n",
" VBox([\n",
" widgets.HTML(value='<h2 align=left>Print Parameters: </h2>'),\n",
" HBox([widgetList3['hl'],widgetList3['nominal_ew'],]),\n",
" HBox([widgetList3['nominal_print_speed'],]),\n",
" HBox([widgetList3['nozzle_temp'],widgetList3['bed_temp'],]),\n",
" HBox([widgetList3['fan_percent'],]),\n",
" HBox([widgetList3['printer_name'],]),\n",
" HBox([widgetList3['design_name'],SaveTripodGcodeButton,tripod_status_widget],)\n",
" ],layout={'border': '1px solid black'}),\n",
" ],titles=['Design','Preview','G-Code'])\n",
"\n",
"env={}\n",
"tripod(**tripod_parameters,env=env)\n",
"update_tripod_plot(**env)\n",
"output9.clear_output(wait=False)#make form smaller \n",
"plt.close()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Capstan"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### alpha_blend(), groove_depth_pattern()"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {
"editable": true,
"jupyter": {
"source_hidden": true
},
"slideshow": {
"slide_type": ""
},
"tags": []
},
"outputs": [],
"source": [
"#groove toolpath\n",
"from math import cos,pi\n",
"def alpha_blend(x,xstart, xend,f0,f1):\n",
" alpha=0.5+0.5*cos(max(0.0,min(1,(x-xstart)/(xend-xstart)))*pi)\n",
" return [alpha*y0+(1.0-alpha)*y1 for y0,y1 in zip(f0(x),f1(x))]\n",
" \n",
"def groove_depth_pattern_factory(*,delta_phi_ramp_start, delta_phi_chamfer_transition, phi_center, delta_phi_ramp,delta_phi_helix,\n",
" r_chamfer, z_bottom, dz_chamfer, r_circ, r_cable_ramp, z_top, r_cable, \n",
" z_helix_start, groove_pitch, phi_helix_start, h_rim, groove_flank_angle,\n",
" R_rim,**_):\n",
" phi_0=delta_phi_ramp_start-delta_phi_chamfer_transition-phi_center\n",
" phi_1=phi_0+delta_phi_chamfer_transition\n",
" phi_2=phi_1+delta_phi_ramp\n",
" phi_3=phi_2+delta_phi_helix\n",
" phi_4=phi_3+delta_phi_ramp\n",
" phi_5=phi_4+delta_phi_chamfer_transition\n",
" #tool paths:\n",
" #variable input parameters\n",
" def groove_toolpath(phi):\n",
" def bottom_chamfer(phi): return [ r_chamfer, z_bottom-dz_chamfer ]\n",
" def bottom_cable(phi): return [ r_cable_ramp, z_bottom-r_cable ]\n",
" def helix(phi): return [ r_circ, (phi-phi_2)/(2*pi)*groove_pitch+h_rim+r_cable ]\n",
" def top_cable(phi): return [ r_cable_ramp, z_top+r_cable ]\n",
" def top_chamfer(phi): return [ r_chamfer, z_top+dz_chamfer ]\n",
" \n",
" if phi<phi_0 : return bottom_chamfer(phi)\n",
" elif (phi>=phi_0)&(phi<phi_1): return alpha_blend(phi,phi_0,phi_1,bottom_chamfer,bottom_cable)\n",
" elif (phi>=phi_1)&(phi<phi_2): return alpha_blend(phi,phi_1,phi_2,bottom_cable,helix)\n",
" elif (phi>=phi_2)&(phi<phi_3): return helix(phi)\n",
" elif (phi>=phi_3)&(phi<phi_4): return alpha_blend(phi,phi_3,phi_4,helix,top_cable)\n",
" elif (phi>=phi_4)&(phi<phi_5): return alpha_blend(phi,phi_4,phi_5,top_cable,top_chamfer)\n",
" elif phi>=phi_5 : return top_chamfer(phi)\n",
" else: raise(Exception('This line should never be reached.'))\n",
" \n",
" def groove_tool(dz,r=0.45,a=30*deg):\n",
" flank=abs(dz)>r*np.cos(a)\n",
" if flank:\n",
" result=abs(dz)/np.tan(a)-r/np.sin(a)\n",
" else:\n",
" result=-(r**2 - dz**2)**0.5\n",
" return result\n",
" \n",
" def phi_groove(z):\n",
" return (z-z_helix_start)/groove_pitch*(2*pi)+phi_helix_start\n",
" \n",
" def thread_depth_pattern(z,phi,z0=0.0):\n",
" i_groove=(phi_groove(z)-pi)//(2*pi)\n",
" phi_tool_1=(phi)%(2*pi)+i_groove*(2*pi)\n",
" r_tool_1,z_tool_1=groove_toolpath(phi_tool_1)\n",
" dz_1=z_tool_1-z\n",
" if abs(dz_1)<=(groove_pitch/2):\n",
" return r_tool_1+groove_tool(dz=dz_1,r=r_cable,a=groove_flank_angle)\n",
" phi_tool_2=phi_tool_1+2*pi\n",
" r_tool_2,z_tool_2=groove_toolpath(phi_tool_2)\n",
" dz_2=z_tool_2-z\n",
" if abs(dz_2)<=(groove_pitch/2):\n",
" return r_tool_2+groove_tool(dz=dz_2,r=r_cable,a=groove_flank_angle)\n",
" while dz_1>0:\n",
" phi_tool_2,r_tool_2,z_tool_2,dz_2=phi_tool_1,r_tool_1,z_tool_1,dz_1\n",
" phi_tool_1=phi_tool_1-2*pi\n",
" r_tool_1,z_tool_1=groove_toolpath(phi_tool_1)\n",
" dz_1=z_tool_1-z\n",
" while dz_2<0:\n",
" phi_tool_1,r_tool_1,z_tool_1,dz_1=phi_tool_2,r_tool_2,z_tool_2,dz_2\n",
" phi_tool_2=phi_tool_2+2*pi\n",
" r_tool_2,z_tool_2=groove_toolpath(phi_tool_2)\n",
" dz_2=z_tool_2-z\n",
" r_2=r_tool_2+groove_tool(dz=dz_2,r=r_cable,a=groove_flank_angle)\n",
" r_1=r_tool_1+groove_tool(dz=dz_1,r=r_cable,a=groove_flank_angle)\n",
" return min(r_1,r_2,R_rim)\n",
" return thread_depth_pattern"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### cpastan()"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {
"jupyter": {
"source_hidden": true
}
},
"outputs": [],
"source": [
"capstan_parameters=dict(n_spokes=13, n_strands=5,\n",
" phi_rim=0.160, r_fillet_rim=0.060, phi_hub=0.10, r_fillet_hub=0.160,\n",
" ew_rim=1.0,ew_fillet_rim=0.8,ew_spokes=0.7,ew_fillet_hub=0.5,ew_hub=0.5,\n",
" spoke_midpoint=0.38,mesh_twist_pitch = -60,\n",
" l_turn= 60.0, l_tot= 600.0, d_cable=0.9, groove_pitch=1.25, left_handed=False, n_cable_channels=2,\n",
" tunnel_pos=0.5,\n",
" hub_squeezeout_factor=2.,shaft_type='D', d_shaft=5.0,D_key=0.5, countersink_chamfer=0.75,\n",
" z_=0.4,\n",
" n_skirt=3,skirt_offset=1.0,hl=0.2,hl_start=0.05,\n",
" #print_parameters\n",
" design_name = 'Capstan',\n",
" nozzle_temp = 220.0, bed_temp = 120.0,\n",
" nominal_print_speed = 10.0*60.0,#10*60 #print slow to give the layer time to cool\n",
" max_print_speed = 15*60,#=speed for ew=0.5mm \n",
" nominal_ew = 0.75, # extrusion width\n",
" fan_percent = 0.0,\n",
" # nominal_eh = 0.2, # extrusion/layer heigth\n",
" printer_name='generic', # generic / ultimaker2plus / prusa_i3 / ender_3 / cr_10 / bambulab_x1 / toolchanger_T0\n",
" )\n",
"\n",
"capstan_parameter_descriptions={'l_turn':'circumference',\n",
" 'l_tot': 'cable groove length',\n",
" 'd_shaft':'shaft diameter'}\n",
"\n",
"from math import pi\n",
"deg=pi/180.0 \n",
"\n",
"def capstan(n_spokes=13, n_strands=5,\n",
" phi_rim=0.160, r_fillet_rim=0.060, phi_hub=0.160, r_fillet_hub=0.060,\n",
" ew_rim=1.0,ew_fillet_rim=0.8,ew_spokes=0.7,ew_fillet_hub=0.5,ew_hub=0.5,\n",
" spoke_midpoint=0.38,mesh_twist_pitch = -60.0,\n",
" l_turn= 60.0, l_tot= 600.0, d_cable=0.9, groove_pitch=1.25, left_handed=False, n_cable_channels=2,\n",
" tunnel_pos=0.5,\n",
" hub_squeezeout_factor=2.25,shaft_type='D', d_shaft=5.0,D_key=0.5, countersink_chamfer=0.75,\n",
" z_=0.4,\n",
" n_skirt=3,skirt_offset=1.0,hl=0.2,hl_start=0.05,\n",
" env=None,**kwargs):\n",
" import cmath\n",
" #calculate dependent parameters\n",
" r_cable=d_cable/2\n",
" r_circ=(l_turn**2-groove_pitch**2)**0.5/(2*pi)\n",
" r_shaft=d_shaft/2\n",
" h_rim=2.0*d_cable\n",
" R_rim=r_circ+r_cable\n",
" r_cable_ramp=r_circ-d_cable-ew_rim\n",
" phi_offset_channel2=0 if n_cable_channels!=2 else (n_spokes//2)/n_spokes*2*pi\n",
" dz_chamfer=1.75*d_cable#vertical tool position above/below top/bottom\n",
" r_chamfer=r_cable_ramp#horizontal tool position\n",
" groove_flank_angle=30*deg\n",
" delta_phi_ramp_start=1.5*(2*pi)/n_spokes #offset relative to return channel center\n",
" delta_phi_ramp=pi/2 #\n",
" delta_phi_chamfer_transition=pi/16\n",
" dz_ends=2*(h_rim+r_cable)\n",
" phi_ends=2*(delta_phi_ramp_start+delta_phi_ramp)+phi_offset_channel2\n",
" delta_phi_helix_=(l_tot/l_turn)*2*pi#preliminary\n",
" n_rot=(dz_ends*(2*pi)+ delta_phi_helix_*groove_pitch - mesh_twist_pitch*(phi_ends + delta_phi_helix_))/(-mesh_twist_pitch*(2*pi))\n",
" n_rot=int(n_rot+1)\n",
" delta_phi_helix=(-dz_ends*(2*pi) - n_rot*mesh_twist_pitch*2*pi + phi_ends*mesh_twist_pitch)/(groove_pitch -mesh_twist_pitch)\n",
" z_bottom=0.0\n",
" z_top=z_bottom+delta_phi_helix/(2*pi)*groove_pitch+dz_ends\n",
" z_helix_start=z_bottom+h_rim+r_cable\n",
" phi_helix_start=delta_phi_ramp_start+delta_phi_ramp\n",
" phi_center=0.5*2*pi/n_spokes\n",
"# phi_width=2*pi/n_spokes\n",
"# max_phase_advance=0.7*2*pi/n_spokes\n",
" \n",
" phi_tot=2*pi*n_strands\n",
" phi_spoke2=phi_tot/(2*n_spokes)\n",
" \n",
" R_hub=r_shaft\n",
" l1,r1,l2,r2=phi_rim*phi_spoke2*R_rim,r_fillet_rim*phi_spoke2*R_rim,phi_hub*phi_spoke2*R_hub,r_fillet_hub*phi_spoke2*R_hub\n",
" c1=(0-1j*(R_rim-r1))*cmath.exp(1j*phi_rim*phi_spoke2)\n",
" c2=(0-1j*(R_hub+r2))*cmath.exp(1j*((1-phi_hub)*phi_spoke2))\n",
" t1,t2=calcTangent(c1,r1,c2,r2)\n",
" ltan,phitan=cmath.polar(t2-t1)\n",
" phitan%=2*pi #counter-clockwise 0-360deg\n",
" arcs=[(l1,phi_rim*phi_spoke2),(r1*(phitan-phi_rim*phi_spoke2),phitan-phi_rim*phi_spoke2),(ltan*(1-spoke_midpoint),0),(ltan*spoke_midpoint,0),(r2*(phitan-((1-phi_hub)*phi_spoke2)),-phitan+((1-phi_hub)*phi_spoke2)),(l2,phi_hub*phi_spoke2)]\n",
" l_layer=sum(l for l,*_ in arcs)*2*n_spokes #extrusion path length for one complete layer (used to calculate z-coordinate)\n",
" arcs=(arcs+arcs[-1::-1])# add mirrored arc sequence\n",
" arc_ew=[ew_rim,ew_fillet_rim,ew_spokes,0.5*ew_spokes+0.5*ew_fillet_hub,ew_fillet_hub,ew_hub]#extrusion widths for rim...hub arc segments\n",
" arc_ew=arc_ew+arc_ew[-1::-1]\n",
" p_spokes_mid=list(Segments2Complex(arcs[:3],p0=R_rim+0.j,a0=0+1j,tol=0.01))[-1][0]\n",
" r_spokes_mid=abs(p_spokes_mid)\n",
" outline=[(R_rim*2*pi,2*pi),]\n",
" p0o=R_rim+0.0j\n",
" n0o=0.0+1.0j\n",
" if shaft_type.upper()=='O':\n",
" DshaftOutline=[(r_shaft*2*pi,2*pi)]#'plain' shaft outline is a circle\n",
" else:\n",
" Dkeycp = (r_shaft**2-(r_shaft-D_key)**2)**0.5 + 1j*(r_shaft-D_key)#corner point of D-shaft (x+ iy)\n",
" Dkeyang=np.log(Dkeycp).imag # angle up to the corner point\n",
" DshaftOutline=[(r_shaft*Dkeyang,Dkeyang),(0,pi/2-Dkeyang),(Dkeycp.real,0)]#1/4 of D-shaft outline\n",
" DshaftOutline=DshaftOutline+DshaftOutline[-1::-1]#add mirror image -> upper half of D-shaft outlone\n",
" DshaftOutline+=DshaftOutline if shaft_type.upper()=='DD' else [(r_shaft*pi,pi)] #add lower half of D-shaft outline\n",
" inline_offset=0.5*ew_hub*hub_squeezeout_factor\n",
" inline=[(l+a*inline_offset,a) for l,a in DshaftOutline]\n",
" p0i=R_hub+inline_offset+0.0j\n",
" n0i=0.0+1.0j\n",
" meshpoints=pipe(\n",
" #1. generate the points of the raw annular mesh:\n",
" lambda:Segments2Complex(arcs,p0=R_rim+0.j,a0=0+1j,tol=0.003,return_start=True,loops=n_spokes if z_!=None else math.inf), \n",
" #2. calculate the z-coordinate based on the total extrusion length, re-arrange the variables:\n",
"# iterize(lambda palx:list(NamedList(p=palx[0],z=z_,eh=hl,ew=arc_ew[palx[3]],))), \n",
" iterize(lambda palx:NamedList(p=palx[0],z=z_ if z_!=None else hl*palx[2]/l_layer+hl_start,eh=hl,ew=arc_ew[palx[3]],a=palx[1],)), \n",
" #3. check if the top (z=h+hl) is reached, and end the iteration if this is the case:\n",
" terminator(stopcondition=lambda p_z_ew: p_z_ew[1] > (z_top+hl) ), \n",
" #4. shift spokes midpoint radially to make room for cable tunnel\n",
" iterize(calibrator(r_ref=r_spokes_mid,dr_offset=R_hub+tunnel_pos*(R_rim-R_hub)-r_spokes_mid,ew_factor=0.0)),\n",
" #4. counterssink\n",
" iterize(calibrator(r_ref=R_hub,f_offset=lambda z,*_,**__:max(0,countersink_chamfer-(z-z_bottom),countersink_chamfer-1.0*(z_top-z)),ew_factor=0.0)),\n",
" #4. rotate the mesh (helical spokes):\n",
" iterize(rotor(fphase=lambda z,*_:1j**(z/mesh_twist_pitch*4))), \n",
" #5. deform the annular mesh to fit between 'inline' and 'outline', and top/bottom chamfer the outside outline:\n",
" iterize(mesh_transformer(R_rim=R_rim,R_hub=R_hub, \n",
" outline=outline,p0o=p0o,n0o=n0o,\n",
" foutline_offset=lambda z,*_,**__:0.0,#max(0,w_chamfer-z,w_chamfer-(h-z)),\n",
" inline=inline,p0i=p0i,n0i=n0i,)),\n",
"\n",
" #9. convert the complex 2D coordinate to real x, y coordinates:\n",
" iterize(lambda pz:(pz[0].real,(-1 if left_handed else 1)*pz[0].imag,*(pz[1:]))),\n",
" )\n",
" def skirt_and_meshpoints():\n",
" skirt=((p[0].real,p[0].imag,hl,hl,0.6) for offs in range(n_skirt) for p in Segments2Complex(outline,p0=R_rim+0.0j,a0=0+1j,tol=0.005,offs=0.5*offs+skirt_offset,return_start=True) )\n",
" for x,y,z,eh,ew in skirt:#skip the first few points so that the start point of the skirt is not near the start point of the print.\n",
" if y>R_rim/4:\n",
" break\n",
" yield from skirt\n",
" yield R_rim,0,hl,0.0,0.0 #move to start of print\n",
" yield from meshpoints()\n",
" if z_==None:\n",
" yield 0.0,0.0,max(z_top+10,30),0.0,0.0 #move print head go to parking position if not layer preview\n",
" capstan_point_factory=skirt_and_meshpoints if ((z_==None) and(n_skirt>0)) or ((z_<=hl) and (n_skirt>0)) else meshpoints\n",
" if env!=None:\n",
" env.update({key:value for key,value in locals().items() if not key in ['env','args','kwargs']})\n",
" return capstan_point_factory\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### update_capstan_plot()"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [],
"source": [
"def update_capstan_plot(*,n_spokes, n_strands, phi_rim, r_fillet_rim, phi_hub, r_fillet_hub, ew_rim,\n",
" ew_fillet_rim, ew_spokes, ew_fillet_hub, ew_hub, spoke_midpoint, mesh_twist_pitch,\n",
" l_turn, l_tot, d_cable, groove_pitch, left_handed, n_cable_channels, tunnel_pos, \n",
" hub_squeezeout_factor, shaft_type, d_shaft, D_key, countersink_chamfer, z_, n_skirt, \n",
" skirt_offset, hl, hl_start, r_cable, r_circ, r_shaft, h_rim, r_cable_ramp, \n",
" phi_offset_channel2, dz_chamfer, r_chamfer, groove_flank_angle, delta_phi_ramp_start,\n",
" delta_phi_ramp, delta_phi_chamfer_transition, dz_ends, phi_ends, delta_phi_helix_, n_rot, \n",
" delta_phi_helix, z_helix_start, phi_helix_start, phi_tot, phi_spoke2, R_hub,\n",
" l1, r1, l2, r2, c1, c2, t1, t2, ltan, phitan, r_spokes_mid, p_spokes_mid, \n",
" capstan_point_factory, arc_ew, arcs, R_rim, z_bottom, z_top, DshaftOutline,\n",
" **__):\n",
" output10.clear_output(wait=True)\n",
" with output10:\n",
" %matplotlib inline\n",
" fig=plt.figure(figsize=(3.5,3.5)) \n",
" ax=fig.add_subplot(1,1,1) \n",
" if phi_spoke2>60*deg:\n",
" ax.plot(0,0,'+')\n",
" for phi in np.exp(1j*np.linspace(0,phi_spoke2,2)):\n",
" p1=-1.0j*(R_hub-0.2*(R_rim-R_hub))*phi\n",
" p2=-1.0j*(R_rim+0.2*(R_rim-R_hub))*phi\n",
" ax.plot((-p1.imag,-p2.imag),(p1.real,p2.real),'k-.',lw=1)\n",
" xlim=ax.get_xlim()\n",
" ylim=ax.get_ylim()\n",
" plotArcchain(ax,R_rim+0j,0.0+1j,((l1,phi_rim*phi_spoke2),(r1*2*pi,2*pi)))\n",
" ax.plot(-c1.imag,c1.real,('k+'))\n",
" plotArcchain(ax,(R_hub)*exp(1j*phi_spoke2),(-1j)*exp(1j*phi_spoke2),((l2,-phi_hub*phi_spoke2),(r2*2*pi,2*pi)))\n",
" ax.plot(-c2.imag,c2.real,'k+') \n",
" ax.plot([-t1.imag,-t2.imag],[t1.real,t2.real]) \n",
" for i in range(n_strands):\n",
" dphi=2*phi_spoke2/n_strands\n",
" phi=exp(-1j*dphi*(i+1))\n",
" plotArcchain(ax,(R_rim)*phi,(1j)*phi,arcs*2,c='lightgray',zorder=-1)\n",
" if n_strands<2:\n",
" for r in [R_hub,R_rim]:\n",
" p=-1j*r*np.exp(1j*np.linspace(0,phi_spoke2,25))\n",
" ax.plot(p.real,p.imag,c='lightgray',lw=1,zorder=-1)\n",
" ax.plot(p_spokes_mid.real,p_spokes_mid.imag,'k.')\n",
" ax.set_xlim(xlim)\n",
" ax.set_ylim(ylim)\n",
" ax.set_aspect('equal')\n",
" ax.set_aspect(1.0)\n",
" display(fig)\n",
" plt.close()\n",
" output11.clear_output(wait=True)\n",
" with output11:\n",
" fig=plt.figure(figsize=(6,6)) \n",
" ax=fig.add_subplot(1,1,1) \n",
" p_mesh=list(zip(*((p.real,p.imag) for p,*_ in Segments2Complex(arcs,p0=R_rim+0.j,a0=0+1j,tol=0.01,return_start=True,loops=n_spokes))))\n",
" ax.plot(p_mesh[0],p_mesh[1],'.-',c='lightgray',label='raw mesh',zorder=-1) \n",
" ax.plot([xlim[0],xlim[1],xlim[1],xlim[0],xlim[0]],[ylim[0],ylim[0],ylim[1],ylim[1],ylim[0]],'--',c='gray',lw=1,label='mesh detail') \n",
"\n",
" transformed_mesh_points=list(zip(*capstan_point_factory()))\n",
" ax.plot(transformed_mesh_points[0],transformed_mesh_points[1],'-',c='g',label='transformed mesh',zorder=1)\n",
" plotArcchain(ax,(-1 if left_handed else 1)*r_shaft,(-1 if left_handed else 1)*1j,DshaftOutline,'b-')\n",
" ax.plot(0,0,'b-',label='shaft')\n",
"# if (n_skirt>0) and (z_<=hl):\n",
"# skirt=list(zip(*((p[0].real,p[0].imag) for offs in range(n_skirt) for p in Segments2Complex(outline,p0=L+R3+0.0j,a0=0+1j,tol=0.005,offs=0.5*offs+1,return_start=True) )))\n",
"# ax.plot(skirt[0],skirt[1],'c-',label='skirt')\n",
" ax.legend(loc='lower right')\n",
"# ax.set_xlim(xlim)\n",
"# ax.set_ylim(ylim)\n",
" ax.set_aspect('equal')\n",
" display(fig)\n",
" plt.close()\n",
" output12.clear_output(wait=True)\n",
" with output12:\n",
"# print(f'{R3+R4=}, {L=}, {R5+R4=}, {sss(R3+R3,L,R5+R4)/deg=}, {phi3/deg=},{phi4/deg=}, {phi5/deg=}, {R1=}')\n",
"# print(f'{len(skirt)=}, {min(skirt[0])=:.2f}, {max(skirt[0])=:.2f}')\n",
"# print(f'{transformed_mesh_points[0][0]=},{transformed_mesh_points[1][0]=}')\n",
" from math import gcd\n",
" if gcd(n_strands,n_spokes)!=1:\n",
" print('\\x1b[31m'+f'n_strands and n_spokes are not coprime: {gcd(n_strands,n_spokes)=} (should be 1)'+'\\x1b[0m')\n",
" print(f'{r_spokes_mid=: 0.3f}')\n",
" print(f'{R_rim=: 0.3f}, {R_hub=:0.3f}, {n_spokes=:d}, {n_strands=:d},')\n",
" print(f'{phi_rim=:0.3f}, {r_fillet_rim=:0.3f}, {phi_hub=:0.3f}, {r_fillet_hub=:0.3f},')\n",
" print()\n",
" print(f'mesh={arcs[:5]}')\n",
" plt.close()\n",
"\n",
"from ipywidgets import widgets,HBox,VBox,Box\n",
"output10=widgets.Output() \n",
"output11=widgets.Output()\n",
"output12=widgets.Output()\n",
"\n",
"def handle_change_capstan(change):\n",
" key,value=change['owner'].key,change['new']\n",
" capstan_parameters[key] = value\n",
" if key=='h': \n",
" widgetList4['z_'].max=capstan_parameters[key]\n",
" env={}\n",
" capstan(**capstan_parameters,env=env)#calculate intermediate parameters\n",
" update_capstan_plot(**env)\n",
" \n",
"def save_capstan_gcode(*args,**kwargs):\n",
" from datetime import datetime\n",
" filename=capstan_parameters['design_name']+datetime.now().strftime(\"__%d-%m-%Y__%H-%M-%S\")\n",
" capstan_status_widget.value=' calculating extrusion path...'\n",
" my_capstan=capstan(**(capstan_parameters|dict(z_=None))) \n",
"# myTripodPoints=list(myTripod())\n",
" steps=list(StepGenerator(my_capstan(),**capstan_parameters))\n",
" xmax,ymax=xmin,ymin=0,0\n",
" for x,y in ((point.x,point.y) for point in steps if type(point)==fc.Point):\n",
" if x!= None: xmin=min(xmin,x)\n",
" if x!= None: xmax=max(xmax,x)\n",
" if y!= None: ymin=min(ymin,y)\n",
" if y!= None: ymax=max(ymax,y)\n",
" \n",
" model_offset = fc.Vector(x=100-0.5*(xmax+xmin), y=100-0.5*(ymax+ymin), z=0.0)\n",
" steps = fc.move(steps, model_offset)\n",
" gcode_controls = fc.GcodeControls(\n",
" printer_name=capstan_parameters['printer_name'],\n",
" save_as=filename,\n",
" include_date=False,\n",
" initialization_data={\n",
" 'primer': 'no_primer',\n",
" }|capstan_parameters)\n",
" capstan_status_widget.value=' saving gcode to \"'+filename+'.gcode\" ...'\n",
" fc.transform(steps, 'gcode', gcode_controls)\n",
" if \"colab\" in globals():\n",
" capstan_status_widget.value=' preparing file for download...'\n",
" colab.files.download(filename+\".gcode\")\n",
" capstan_status_widget.value=''\n",
" \n",
"def update_capstan_preview(*args,**kwargs):\n",
" import os\n",
" preview_output.clear_output(wait=True)\n",
" with preview_output:\n",
" my_capstan=capstan(**(capstan_parameters|dict(z_=None))) \n",
" # myTripodPoints=list(myTripod())\n",
" capstan_status_widget.value='calculating extrusion path ...'\n",
" steps=list(StepGenerator(my_capstan(),**capstan_parameters))\n",
" xmax,ymax=xmin,ymin=0,0\n",
" for x,y in ((point.x,point.y) for point in steps if type(point)==fc.Point):\n",
" if x!= None: xmin=min(xmin,x)\n",
" if x!= None: xmax=max(xmax,x)\n",
" if y!= None: ymin=min(ymin,y)\n",
" if y!= None: ymax=max(ymax,y)\n",
" model_offset = fc.Vector(x=100-0.5*(xmax+xmin), y=100-0.5*(ymax+ymin), z=0.0)\n",
" steps = fc.move(steps, model_offset)\n",
" style='tube'\n",
" if ('iPad' in os.uname().machine):\n",
" plt.close()\n",
" capstan_status_widget.value='running fc.transform...'\n",
" fc.transform(steps, 'plot', fc.PlotControls(style='line',color_type='print_sequence'))\n",
" capstan_status_widget.value=''\n",
" _=plt.show()\n",
" else:\n",
" capstan_status_widget.value='running fc.transform...'\n",
" _=fc.transform(steps, 'plot', fc.PlotControls(style=style ,color_type='print_sequence'))\n",
" capstan_status_widget.value=''\n",
" \n",
"style={'description_width':'1.5in'}\n",
"layout={'width':'2.2in'}\n",
"widgetList4={}\n",
"for key,value in capstan_parameters.items():\n",
" if type(value)==int:\n",
" widgetList4[key]=widgets.IntText(description=key, value=value,style=style,layout=layout, readout_format='d')\n",
" elif type(value)==float:\n",
" widgetList4[key]=widgets.FloatText(description=capstan_parameter_descriptions.get(key,key), value=value,style=style,layout=layout, readout_format='0.3f')\n",
" elif type(value)==str:\n",
" widgetList4[key]=widgets.Text(description=key, value=value,style=style,layout=layout,)\n",
" elif type(value)==bool:\n",
" widgetList4[key]=widgets.Dropdown(description=key,options=[True,False], value=value,style=style,layout=layout,indent=True)\n",
" else: print(f'need to add widget for {key=}, {type(value)=}');continue\n",
" \n",
"env={}\n",
"capstan(**capstan_parameters,env=env) \n",
"\n",
"key='z_'\n",
"widgetList4[key]=widgets.FloatSlider(description=key,\n",
" min=env['z_bottom'],max=env['z_top'],value=capstan_parameters['z_'],step=0.2,continuous_update=True, orientation='vertical', \n",
" readout_format='0.1f',layout={'height':'4.5in'})\n",
"key='shaft_type'\n",
"widgetList4[key]=widgets.Dropdown(description=key,\n",
" options=['O','D','DD'],value=capstan_parameters['shaft_type'],layout=layout,style=style)\n",
"\n",
"for key in capstan_parameters: \n",
" widgetList4[key].observe(handle_change_capstan,'value') \n",
" widgetList4[key].key=key\n",
"SaveCapstanGcodeButton=widgets.Button(description='download G-Code',on_click=save_capstan_gcode)\n",
"SaveCapstanGcodeButton.on_click(save_capstan_gcode)\n",
"UpdateCapstanPreviewButton=widgets.Button(description='update preview',on_click=update_capstan_preview)\n",
"UpdateCapstanPreviewButton.on_click(update_capstan_preview)\n",
"capstan_status_widget=widgets.Label()\n",
"Layout4=widgets.Tab([\n",
" VBox([ \n",
" HBox([VBox([\n",
" widgets.HTML(value='<h2 align=left>Mesh</h2>'),\n",
" HBox([widgetList4['n_spokes'],widgetList4['n_strands'],]),\n",
" HBox([widgetList4['l_turn'],widgetList4['d_shaft'],]),\n",
" HBox([widgetList4['phi_rim'],widgetList4['ew_rim'],]),\n",
" HBox([ widgetList4['r_fillet_rim'],widgetList4['ew_fillet_rim'],]),\n",
" HBox([widgetList4['spoke_midpoint'],widgetList4['ew_spokes'],]),\n",
" HBox([widgetList4['r_fillet_hub'],widgetList4['ew_fillet_hub'],]),\n",
" HBox([widgetList4['phi_hub'],widgetList4['ew_hub'],],),\n",
" ]),\n",
" output10,\n",
" ],layout={'border': '1px solid black'}),\n",
" HBox([VBox([\n",
" widgets.HTML(value='<h2 align=left>Capstan Geometry: </h2>'),\n",
" widgetList4['l_tot'],widgetList4['l_turn'],widgetList4['groove_pitch'],widgetList4['d_shaft'],widgetList4['shaft_type'],widgetList4['D_key'],\n",
" widgetList4['mesh_twist_pitch'], widgetList4['tunnel_pos'],widgetList4['countersink_chamfer'],widgetList4['hub_squeezeout_factor'],\n",
" widgetList4['left_handed'],\n",
" widgetList4['n_skirt'],widgetList4['skirt_offset'],\n",
" ],),\n",
" HBox([output11,widgetList4['z_']]),\n",
" ],layout={'border': '1px solid black'}),\n",
" HBox([VBox([\n",
"# widgets.HTML(value='<h2 align=left>Output: </h2>'),#title of output cell \n",
" output12,\n",
" ]),\n",
" ],layout={'border': '1px solid black'} ),\n",
" ]),\n",
" HBox([ \n",
" VBox([\n",
" widgets.HTML(value='<h2 align=left>Print Parameters: </h2>'),\n",
" HBox([widgetList4['hl'],UpdateCapstanPreviewButton,capstan_status_widget],)\n",
" ]),\n",
" # tripod_preview_output, #plotly output not showing inside HBox\n",
" ],layout={'border': '1px solid black'}),\n",
" VBox([\n",
" widgets.HTML(value='<h2 align=left>Print Parameters: </h2>'),\n",
" HBox([widgetList4['hl'],widgetList4['nominal_ew'],]),\n",
" HBox([widgetList4['nominal_print_speed'],]),\n",
" HBox([widgetList4['nozzle_temp'],widgetList4['bed_temp'],]),\n",
" HBox([widgetList4['fan_percent'],]),\n",
" HBox([widgetList4['printer_name'],]),\n",
" HBox([widgetList4['design_name'],SaveCapstanGcodeButton,capstan_status_widget],)\n",
" ],layout={'border': '1px solid black'}),\n",
" ],titles=['Design','Preview','G-Code'])\n",
"\n",
"update_capstan_plot(**env);\n",
"output12.clear_output(wait=False)#make form smaller \n",
"plt.close()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Interactive Section"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [],
"source": [
"Layout=widgets.Tab([Layout1,Layout2,Layout3,Layout4],selected_index=3)\n",
"Layout.set_title(0,'Linear Mesh')\n",
"Layout.set_title(1,'Circular Mesh')\n",
"Layout.set_title(2,'Tripod')\n",
"Layout.children[2].set_title(0,'Design')#colab, for some reason, shows empty Tab titles if not explicitly set like this\n",
"Layout.children[2].set_title(1,'Preview')\n",
"Layout.children[2].set_title(2,'G-Code')\n",
"Layout.set_title(3,'Capstan')\n",
"Layout.children[3].set_title(0,'Design')#colab, for some reason, shows empty Tab titles if not explicitly set like this\n",
"Layout.children[3].set_title(1,'Preview')\n",
"Layout.children[3].set_title(2,'G-Code')\n"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "a7a326f12ca741c2ae6b613b73200104",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Tab(children=(VBox(children=(HBox(children=(VBox(children=(HTML(value='<h2>Parameters</h2>'), FloatSlider(valu…"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"display(Layout);"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "3b39a070f31543a2a71eca58c177da9f",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Output()"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"if \"colab\" in globals(): colab.output.enable_custom_widget_manager()\n",
"display(preview_output)\n",
"if \"colab\" in globals(): colab.output.disable_custom_widget_manager()"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {},
"outputs": [
{
"ename": "Exception",
"evalue": "Exception raised to stop execution of the remaining cells in tis Notebook",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mException\u001b[0m Traceback (most recent call last)",
"Cell \u001b[0;32mIn[23], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mException\u001b[39;00m(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mException raised to stop execution of the remaining cells in tis Notebook\u001b[39m\u001b[38;5;124m'\u001b[39m)\n",
"\u001b[0;31mException\u001b[0m: Exception raised to stop execution of the remaining cells in tis Notebook"
]
}
],
"source": [
"raise Exception('Exception raised to stop execution of the remaining cells in this Notebook')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"jupyter": {
"source_hidden": true
}
},
"outputs": [],
"source": [
"print_parameters=dict(\n",
"design_name = 'Tripod',\n",
"nozzle_temp = 220,\n",
"bed_temp = 120,\n",
"nominal_print_speed = 20*60,#10*60 #print slow to give the layer time to cool\n",
"nominal_ew = 0.75, # extrusion width\n",
"fan_percent = 0,\n",
"nominal_eh = 0.2, # extrusion/layer heigth\n",
"printer_name='generic', # generic / ultimaker2plus / prusa_i3 / ender_3 / cr_10 / bambulab_x1 / toolchanger_T0\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### calculate Tripod extrusion path"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import time\n",
"t0=time.perf_counter()\n",
"my_model=capstan\n",
"my_model_parameters=capstan_parameters \n",
"my_model_point_generator=my_model(**(my_model_parameters|dict(z_=None))) \n",
"my_model_points=list(my_model_point_generator( ))\n",
"t1=time.perf_counter()\n",
"steps=list(StepGenerator(my_model_points,**my_model_parameters))\n",
"t2=time.perf_counter()\n",
"print(f'time to generate extrusion path coordinates: {t1-t0:.3f}s, time to generate fc.Points from coordinates: {t2-t1:.3f}s ')\n",
"len(steps)\n",
"xmax,ymax=xmin,ymin=0,0\n",
"for x,y in ((point.x,point.y) for point in steps if type(point)==fc.Point):\n",
" if x!= None: xmin=min(xmin,x)\n",
" if x!= None: xmax=max(xmax,x)\n",
" if y!= None: ymin=min(ymin,y)\n",
" if y!= None: ymax=max(ymax,y)\n",
"\n",
"model_offset = fc.Vector(x=100-0.5*(xmax+xmin), y=100-0.5*(ymax+ymin), z=0.0)\n",
"steps = fc.move(steps, model_offset)\n",
"steps.append(fc.Point(x=100,y=100))"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"my_model_points[20000]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### show Tripod preview"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# add annotations and plot\n",
"import os\n",
"style='tube'\n",
"import time \n",
"t0=time.perf_counter()\n",
"fc.transform(steps, 'plot', fc.PlotControls(style=style if not ('iPad' in os.uname().machine) else 'line',color_type='print_sequence'));\n",
"t1=time.perf_counter()\n",
"print(f'Time to cerate the FullControl preview: {(t1-t0):0.3f}s') "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Generate Tripod gcode"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"jupyter": {
"source_hidden": true
}
},
"outputs": [],
"source": [
"gcode_controls = fc.GcodeControls(\n",
" printer_name=print_parameters['printer_name'],\n",
" save_as=print_parameters['design_name'],\n",
" initialization_data={\n",
" 'primer': 'no_primer',\n",
" }|print_parameters)\n",
"import time \n",
"t0=time.perf_counter()\n",
"#gcode = fc.transform(steps, 'gcode', gcode_controls)\n",
"t1=time.perf_counter()\n",
"print(f'Time to generate G-Code file from FullControl steps: {(t1-t0):0.3f}s') "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Appendix"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Test Layer Preview"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"jupyter": {
"source_hidden": true
},
"scrolled": true
},
"outputs": [],
"source": [
"from time import perf_counter\n",
"t1=perf_counter()\n",
"myTripod=tripod(**tripod_parameters)\n",
"p=list(myTripod())\n",
"t2=perf_counter()\n",
"print(f'Time to generate the points for one layer: {t2-t1}s')\n",
"import cProfile\n",
"cProfile.run('p=list(myTripod())',sort=1)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"jupyter": {
"source_hidden": true
}
},
"outputs": [],
"source": [
"from matplotlib import pyplot as plt\n",
"xy=list(zip(*p))\n",
"plt.plot(*xy[:2])\n",
"plt.gca().set_aspect('equal')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"jupyter": {
"source_hidden": true
},
"scrolled": true
},
"outputs": [],
"source": [
"mesh=[(1.9332877868244882, 0.19332877868244883), (0.9388633012258815, 1.2950143724065095), (7.473407339055301, 0), (0.28796015806691877, -0.3404535276619185), (0.2114533516839284, 0.060415243338265257)]\n",
"mesh=mesh[:2]+[(mesh[2][0]/2,0)]*2+mesh[3:]#split the spoke in half\n",
"plotArcchain(plt.gca(),10,1j,(mesh+mesh[-1::-1])*13,'b.-')\n",
"plt.gca().set_aspect('equal') "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"jupyter": {
"source_hidden": true
}
},
"outputs": [],
"source": [
"mesh=[(0.15, 0), (0.10412385590811431, 1.041238559081143), (1.0440306508910548, 0), (0.10412385590811431, -1.041238559081143), (0.15, 0)]\n",
"plotArcchain(plt.gca(),0,1,(mesh+mesh[-1::-1])*3,'b.-')\n",
"plt.gca().set_aspect('equal') "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"jupyter": {
"source_hidden": true
}
},
"outputs": [],
"source": [
"from matplotlib import pyplot as plt\n",
"import numpy as np\n",
"z=np.linspace(0,6,200)\n",
"M8_int=ISO_thread(Pitch=1.25)\n",
"M8_ext=ISO_thread(Pitch=1.25,External=True)\n",
"plt.plot([M8_int(z_,0.5)+4 for z_ in z],z,'r-',label='internal thread (nut)')\n",
"plt.plot([M8_ext(z_,0.5)-0.1+4 for z_ in z],z,'g-',label='external thread (bolt)')\n",
"plt.plot([-(M8_int(z_)+4) for z_ in z],z,'r-')\n",
"plt.plot([-(M8_ext(z_)-0.1+4) for z_ in z],z,'g-')\n",
"plt.title('ISO-thread(Pitch=1.25)')\n",
"plt.legend()\n",
"plt.gca().set_aspect('equal')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"jupyter": {
"source_hidden": true
}
},
"outputs": [],
"source": [
"from timeit import timeit\n",
"cos_sin=pipe(iterize(lambda x:1j**(x*4/(2*pi))), iterize(lambda x:(x.real,x.imag)))\n",
"print(*('/'.join(f'{x:.5f}' for x in sc) for sc in cos_sin(iter(range(7)))),sep=', ')\n",
"print(timeit(lambda:list(cos_sin(iter(range(1000)))),number=1000))\n",
"def pipetest(x=None,/,*,description,function):\n",
" print(f'initializing \"{description}\"')\n",
" def wrapper(x,/):\n",
" print(f'in wrapper \"{description}\"')\n",
" for xi in x:\n",
" print(f'yielding {description}({xi})={function(xi)}')\n",
" yield function(xi)\n",
" print(f'post yield {description}({xi})')\n",
" print(f'post loop \"{description}\"') \n",
" return\n",
" return wrapper(x) if x!=None else wrapper\n",
"import cmath \n",
"print('p=pipe(...):')\n",
"p=pipe((pipetest(description='inc',function=lambda x:x+1)),None,iterize(iterize(None)),iterize(lambda x:2*x),pipetest(description='sin',function=cmath.sin),)\n",
"print('piter=p(iter(...)):')\n",
"piter=p(iter([0,10,20.0,30+0.1j]))\n",
"print('\\ncalling next(piter):\\n')\n",
"x=next(piter) \n",
"print(f'x={x}')\n",
"print(f'\\nrunning the loop:\\n')\n",
"for i,x in enumerate(piter):\n",
" print(f'x{i}={x}\\n')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"jupyter": {
"source_hidden": true
}
},
"outputs": [],
"source": [
"import numpy as np\n",
"from matplotlib import pyplot as plt\n",
"def plot_depthpattern(ax5,*,z_bottom,z_top,hl,thread_depth_pattern,r_circ,r_cable,**_):\n",
" z=np.arange(z_bottom,z_top+hl,hl)\n",
" phi=np.linspace(0.0,2*pi,300)\n",
" Phi,Z=np.meshgrid(phi,z)\n",
" R=np.vectorize(thread_depth_pattern)(Z,Phi)\n",
" ax5.imshow(R, interpolation='bilinear', cmap='gray',\n",
" origin='lower', extent=[0,r_circ*2*pi, z_bottom, z_top],\n",
" vmax=r_circ+r_cable, vmin=r_circ-1.5*r_cable)\n",
" ax5.set_title('Depth Map')\n",
" ax5.set_ylabel('axial position [mm]')\n",
" ax5.set_xlabel('circumferential position [mm]')\n",
" ax5.set_aspect('equal')\n",
"fig=plt.figure(figsize=(12,18))\n",
"ax5=fig.add_subplot(1,1,1)\n",
"env={}\n",
"capstan(**capstan_parameters,env=env)\n",
"thread_depth_pattern=groove_depth_pattern_factory(**env)\n",
"plot_depthpattern(ax5,**env,thread_depth_pattern=thread_depth_pattern)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"jupyter": {
"source_hidden": true
}
},
"outputs": [],
"source": [
"widgetList4['l_tot'].value"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"jupyter": {
"source_hidden": true
}
},
"outputs": [],
"source": [
"debug_view = widgets.Output(layout={'border': '1px solid black'})\n",
"\n",
"@debug_view.capture(clear_output=True)\n",
"def bad_callback(event):\n",
" print('This is about to explode')\n",
" return 1.0 / 0.0\n",
"\n",
"button = widgets.Button(\n",
" description='click me to raise an exception',\n",
" layout={'width': '300px'}\n",
")\n",
"button.on_click(bad_callback)\n",
"VBox([button,debug_view])"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"jupyter": {
"source_hidden": true
}
},
"outputs": [],
"source": [
"env={}\n",
"tripod(**tripod_parameters,env=env)\n",
"', '.join(env.keys())"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"jupyter": {
"source_hidden": true
}
},
"outputs": [],
"source": [
"from IPython.display import display, HTML\n",
"display(HTML(\"<style>div.output_scroll { height: 44em; }</style>\"))"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"jupyter": {
"source_hidden": true
}
},
"outputs": [],
"source": [
"display(preview_output)\n",
"update_tripod_preview()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import plotly.graph_objs as go\n",
"from ipywidgets import interact\n",
"fig = go.FigureWidget()\n",
"scatt = fig.add_scatter()\n",
"scatt=scatt.data[0]\n",
"fig"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\"colab\" in globals()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.0"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment