import type { CadKernel, CadParameters, Manifold, Vec2 } from "./kernel"; import { buttonProfile, capProfile, codesText, FIBER_TOOL_IDS, gaugeOutline, needleCaseProfile, validateFiberTool, WPI_REFERENCE } from "../fiber-tools"; // An original compact pixel alphabet. Rectangular strokes engrave actual solid // surfaces; no external fonts, meshes, characters, or source art are loaded. const FONT:Record={ A:"01110/10001/10001/11111/10001/10001/10001",B:"11110/10001/10001/11110/10001/10001/11110",C:"01111/10000/10000/10000/10000/10000/01111",D:"11110/10001/10001/10001/10001/10001/11110",E:"11111/10000/10000/11110/10000/10000/11111",F:"11111/10000/10000/11110/10000/10000/10000",G:"01111/10000/10000/10111/10001/10001/01111",H:"10001/10001/10001/11111/10001/10001/10001",I:"11111/00100/00100/00100/00100/00100/11111",J:"00111/00010/00010/00010/10010/10010/01100",K:"10001/10010/10100/11000/10100/10010/10001",L:"10000/10000/10000/10000/10000/10000/11111",M:"10001/11011/10101/10101/10001/10001/10001",N:"10001/11001/10101/10011/10001/10001/10001",O:"01110/10001/10001/10001/10001/10001/01110",P:"11110/10001/10001/11110/10000/10000/10000",Q:"01110/10001/10001/10001/10101/10010/01101",R:"11110/10001/10001/11110/10100/10010/10001",S:"01111/10000/10000/01110/00001/00001/11110",T:"11111/00100/00100/00100/00100/00100/00100",U:"10001/10001/10001/10001/10001/10001/01110",V:"10001/10001/10001/10001/10001/01010/00100",W:"10001/10001/10001/10101/10101/11011/10001",X:"10001/10001/01010/00100/01010/10001/10001",Y:"10001/10001/01010/00100/00100/00100/00100",Z:"11111/00001/00010/00100/01000/10000/11111", "0":"01110/10001/10011/10101/11001/10001/01110","1":"00100/01100/00100/00100/00100/00100/01110","2":"01110/10001/00001/00010/00100/01000/11111","3":"11110/00001/00001/01110/00001/00001/11110","4":"00010/00110/01010/10010/11111/00010/00010","5":"11111/10000/10000/11110/00001/00001/11110","6":"01110/10000/10000/11110/10001/10001/01110","7":"11111/00001/00010/00100/01000/01000/01000","8":"01110/10001/10001/01110/10001/10001/01110","9":"01110/10001/10001/01111/00001/00001/01110","-":"00000/00000/00000/11111/00000/00000/00000","+":"00000/00100/00100/11111/00100/00100/00000",".":"00000/00000/00000/00000/00000/00100/00100" }; export function textSection(c:CadKernel,text:string,x:number,y:number,pixel:number) { const strokes=[]; for(const [i,ch] of [...text].entries()) for(const [row,line] of (FONT[ch]??"").split("/").entries()) { // Contiguous runs avoid seams inside letter strokes. for(const match of line.matchAll(/1+/g)) strokes.push(c.move2(c.rect(match[0].length*pixel,pixel),[x+(i*6+match.index!+match[0].length/2)*pixel,y-row*pixel])); } return strokes.length?c.add2(...strokes):undefined; } export function buildFiberTool(id:string,values:CadParameters,c:CadKernel):Manifold|undefined { if(!(FIBER_TOOL_IDS as readonly string[]).includes(id))return; validateFiberTool(id,values); const p=values as Record; const stroke=(points:Vec2[],width:number)=>{ const pieces=points.map(pt=>c.move2(c.circle(width/2,12),pt)); for(let i=1;ic.move2(c.circle(h.radius,24),[h.x,h.y]))); let button=c.extrude(face,p.thickness); const marks=[]; if(s===1) for(const sign of [-1,1])marks.push(stroke([[sign*d*.14,-d*.3],[sign*d*.22,0],[sign*d*.14,d*.3]],Math.max(.55,d*.022))); if(s===3) {marks.push(stroke([[0,d*.43],[0,d*.22]],.7),stroke([[0,-d*.43],[0,-d*.22]],.7));for(const sign of [-1,1]) for(const yy of [-.18,.16])marks.push(c.move2(c.circle(d*.06,20),[sign*d*.28,yy*d]));} if(s===5) {marks.push(stroke([[d*.27,d*.27],[d*.39,d*.17],[d*.41,d*.02]],.8));marks.push(c.move2(c.circle(Math.max(.5,d*.02),16),[d*.32,d*.23]));} if(s===6)for(let i=0;i<5;i++){const a=b.holes[i%p.holes],o=b.holes[(i+2)%p.holes];const len=Math.hypot(o.x-a.x,o.y-a.y),t=(p.hole_diameter/2+.7)/len;if(t<.45)marks.push(stroke([[a.x+(o.x-a.x)*t,a.y+(o.y-a.y)*t],[o.x+(a.x-o.x)*t,o.y+(a.y-o.y)*t]],.6));} if([7,8,12,13,17].includes(s)) { for(const sign of [-1,1])marks.push(c.move2(c.circle(d*(s===13?.08:.045),20),[sign*d*.23,d*.2])); if([7,8].includes(s))marks.push(c.polygon([[-d*.05,d*.08],[0,0],[d*.05,d*.08]])); if([12,13].includes(s))for(const xx of [-.15,0,.15])marks.push(stroke([[xx*d,-d*.32],[xx*d,-d*.43]],.7)); if(s===17)for(const sign of [-1,1])marks.push(stroke([[sign*d*.17,-d*.12],[sign*d*.39,0],[sign*d*.31,-d*.28]],.7)); } if(s===10)marks.push(stroke([[0,-d*.4],[0,d*.4]],.65),...[-1,1].flatMap(sign=>[-.17,.05,.23].map(y=>stroke([[0,y*d],[sign*d*.18,(y+.1)*d]],.5)))); if(s===11) for(let i=0;i<5;i++){const a=i*Math.PI*2/5;marks.push(stroke([[Math.cos(a)*d*.31,Math.sin(a)*d*.31],[Math.cos(a)*d*.43,Math.sin(a)*d*.43]],.65));} if(s===16)for(let i=0;i<5;i++){const a=i*Math.PI*2/5;marks.push(c.move2(c.circle(d*.035,16),[Math.cos(a)*d*.35,Math.sin(a)*d*.35]));} if(marks.length)button=c.difference(button,c.move(c.extrude(c.add2(...marks),.65),[0,0,p.thickness-.55])); if(s===4){const petals=Array.from({length:5},(_,i)=>{const a=i*Math.PI*2/5;return c.move(c.extrude(c.move2(c.rotate2(c.scale2(c.circle(d*.16,32),[1,.6]),a*180/Math.PI),[Math.cos(a)*d*.28,Math.sin(a)*d*.28]),1.5),[0,0,p.thickness-.05]);});button=c.union(button,...petals);} if(s>=18)button=c.union(button,...Array.from({length:4},(_,i)=>{const a=Math.PI/4+i*Math.PI/2;return c.move(c.cylinder(8,Math.max(.9,d*.04),Math.max(.9,d*.04)*.9,24),[Math.cos(a)*d*.29,Math.sin(a)*d*.29,p.thickness-.05]);})); if(!p.shank)button=c.difference(button,...b.holes.map(h=>c.move(c.cylinder(p.thickness+10,h.radius,h.radius,24),[h.x,h.y,-.1]))); if(p.shank){const h=p.hole_diameter+3,yy=b.centerHole?d*.355:0;button=c.move(button,[0,0,h]);const loop=c.move(c.box(p.hole_diameter+4,3,h+.1),[0,yy,0]);const eye=c.move(c.rotate(c.cylinder(5,p.hole_diameter/2,p.hole_diameter/2,32),[90,0,0]),[0,yy+2.5,h/2]);button=c.union(button,c.difference(loop,eye));} return c.union(...b.offsets.map(([x,y])=>c.move(button,[x,y,0]))); } if(id==="knitting_needle_caps") { const q=capProfile(values),h=p.depth+2.4; const cap=c.difference(c.cylinder(h,q.outer),c.move(c.cylinder(p.depth+.1,q.bore,q.bore,64),[0,0,2.4]),c.move(c.cylinder(1,q.bore,q.bore+.6,64),[0,0,h-1])); return c.union(...q.offsets.map(([x,y])=>c.move(cap,[x,y,0]))); } if(id==="wpi_yarn_gauge") { let shape=c.polygon(gaugeOutline(p.shape)); // Horizontal opposing datum edges are exactly one inch apart. shape=c.sub2(shape,c.move2(c.rect(40,25.4),[-52,0])); let gauge=c.extrude(shape,p.thickness); const lines=[textSection(c,"WPI 1 INCH",-29,37,.65),...WPI_REFERENCE.map((line,i)=>textSection(c,line,-29,27-i*6.6,.57)),textSection(c,codesText(values.personalization),-29,-33,.65)].filter(x=>!!x); const legend=c.add2(...lines); gauge=c.difference(gauge,c.move(c.extrude(legend,.7),[0,0,p.thickness-.6])); return gauge; } if(id==="double_pointed_needle_box"||id==="needle_garage") { const q=needleCaseProfile(id,values),{length:l,width:w,height:h,wall:t}=q; let tray=c.difference(c.box(l,w,h),c.move(c.box(l-2*t,w-2*t,h),[0,0,t])); if(id==="double_pointed_needle_box")tray=c.union(tray,...Array.from({length:p.count-1},(_,i)=>c.move(c.box(l-2*t,.8,1.2),[0,-q.innerWidth/2+(i+1)*(p.needle_diameter+1.4),t-.05]))); let lid:Manifold; if(p.lid===0){ const grooves=[-1,1].map(sign=>c.move(c.box(l-2*t+1,1.5,2.8),[0,sign*(w/2-2.05),h-3.6])); tray=c.difference(tray,...grooves,c.move(c.box(t+1,w-2.6,4),[l/2-t/2,0,h-3.6])); lid=c.box(q.lidLength,q.lidWidth,2.4); lid=c.union(lid,c.move(c.box(5,q.lidWidth*.55,1.2),[q.lidLength/2-5,0,2.35])); }else{ tray=c.difference(tray,...[-1,1].map(sign=>c.move(c.sphere(.85,24),[sign*l/2,0,h-2.8]))); const skirt=c.difference(c.box(q.lidLength,q.lidWidth,5),c.move(c.box(l+.6,w+.6,6),[0,0,-.5])); lid=c.union(c.box(q.lidLength,q.lidWidth,2.2),c.move(skirt,[0,0,2.15])); const reliefs=[-1,1].flatMap(sign=>[-1,1].map(side=>c.move(c.box(3,1,5),[sign*(l/2+1.3),side*6,3]))); lid=c.difference(lid,...reliefs,c.move(c.box(l-18,3,5),[0,w/2+1.3,2.2])); lid=c.union(lid,...[-1,1].map(sign=>c.move(c.sphere(.65,20),[sign*(l/2+.25),0,4.8]))); } tray=c.difference(tray,c.move(c.box(l-18,t+1,p.project_slot+5),[0,-w/2+t/2,h-3.6-p.project_slot])); return c.union(tray,c.move(lid,[0,q.lidY,0])); } // Original external corner bracket: two or three orthogonal mounting faces, // two triangular end gussets, M3-M6 T-nut screw clearance and head recesses. const s=p.profile*2,t=p.thickness; const plates=[c.box(s,s,t),c.move(c.box(s,t,s),[0,-s/2+t/2,0])]; if(p.ways===3)plates.push(c.move(c.box(t,s,s),[-s/2+t/2,0,0])); const triangle=c.polygon([[-s/2+t,t],[s/2,t],[-s/2+t,s]]); for(const sign of [-1,1])plates.push(c.move(c.rotate(c.extrude(triangle,3),[90,0,90]),[sign>0?s/2-3:-s/2,0,0])); const holes=[]; for(const xx of [-s*.22,s*.22]){ holes.push(c.move(c.cylinder(t+2,(p.bolt+.5)/2),[xx,s*.12,-1])); holes.push(c.move(c.rotate(c.cylinder(t+2,(p.bolt+.5)/2),[90,0,0]),[xx,-s/2+t+1,s*.58])); holes.push(c.move(c.cylinder(1.4,p.bolt+.7),[xx,s*.12,t-1.3])); holes.push(c.move(c.rotate(c.cylinder(1.4,p.bolt+.7),[90,0,0]),[xx,-s/2+t+.1,s*.58])); if(p.ways===3){holes.push(c.move(c.rotate(c.cylinder(t+2,(p.bolt+.5)/2),[0,90,0]),[-s/2-1,xx,s*.58]));holes.push(c.move(c.rotate(c.cylinder(1.4,p.bolt+.7),[0,90,0]),[-s/2+t-1.3,xx,s*.58]));} } return c.difference(c.union(...plates),...holes); }