Created
April 17, 2024 19:31
-
-
Save RichardPotthoff/0b9d624e5503d78e3d19541babcd9c72 to your computer and use it in GitHub Desktop.
generate g-code for a capstan
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "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