"""Line drawings of a part: visible edges solid, hidden edges dashed.This is how engineering drawings show a part, and it is what the report and themanual print. build123d does the hard work: project_to_viewport projects everyedge of the solid onto a flat viewing plane and sorts the results into edges youcan see and edges hidden behind material (hidden-line removal). This module onlysamples those edges into points and draws them. python -m hwe.cad.drawing draws the current design's arm into out/cad/Two outputs from one projection:- draw(): a PNG or PDF through matplotlib (for the HTML report);- tikz(): TikZ path commands (for the book's figures, so fonts and line weights match)."""from__future__importannotationsfrompathlibimportPathimportnumpyasnp# Viewing directions: where the eye sits relative to the part's centre.ISOMETRIC=(-1.0,-1.0,1.0)# the classic isometric viewPLAN=(0.0,0.0,1.0)# looking straight down (top view)defprojected(part,view=ISOMETRIC,samples:int=32):"""Project `part` along `view`. Returns (visible, hidden): lists of Nx2 point arrays."""box=part.bounding_box()centre=box.center()distance=4.0*max(box.size.X,box.size.Y,box.size.Z)direction=np.array(view,dtype=float)direction/=np.linalg.norm(direction)origin=tuple(np.array([centre.X,centre.Y,centre.Z])+distance*direction)up=(0,1,0)iftuple(view)==PLANelse(0,0,1)visible,hidden=part.project_to_viewport(origin,viewport_up=up,look_at=tuple(centre))defpolylines(edges):out=[]foredgeinedges:n=2ifedge.geom_type.name=="LINE"elsesamplespts=[edge.position_at(u)foruinnp.linspace(0.0,1.0,n)]out.append(np.array([[p.X,p.Y]forpinpts]))returnoutreturnpolylines(visible),polylines(hidden)defdraw(part,path:Path|str,view=ISOMETRIC,*,width_in:float=6.0,dpi:int=220,show_hidden:bool=True)->Path:"""Save a line drawing of `part` (PNG or PDF, chosen by the file extension)."""importmatplotlibmatplotlib.use("Agg")importmatplotlib.pyplotaspltvisible,hidden=projected(part,view)everything=np.vstack(visible+hidden)lo,hi=everything.min(axis=0),everything.max(axis=0)aspect=(hi[1]-lo[1])/max(hi[0]-lo[0],1e-9)fig,ax=plt.subplots(figsize=(width_in,max(0.8,width_in*aspect)),dpi=dpi)ifshow_hidden:forlineinhidden:ax.plot(line[:,0],line[:,1],color="#8a97a3",linewidth=0.6,linestyle=(0,(3,2)))forlineinvisible:ax.plot(line[:,0],line[:,1],color="#173446",linewidth=1.1,solid_capstyle="round")ax.set_aspect("equal")ax.set_axis_off()fig.subplots_adjust(0,0,1,1)path=Path(path)path.parent.mkdir(parents=True,exist_ok=True)fig.savefig(path,transparent=True,bbox_inches="tight",pad_inches=0.03)plt.close(fig)returnpathdeftikz(part,view=ISOMETRIC,*,width_cm:float=12.0,show_hidden:bool=True)->str:"""TikZ commands for a line drawing `width_cm` wide (styles: visible, hidden)."""visible,hidden=projected(part,view)everything=np.vstack(visible+hidden)lo,hi=everything.min(axis=0),everything.max(axis=0)scale=width_cm/max(hi[0]-lo[0],1e-9)defpath(line):pts=(line-lo)*scalereturn" -- ".join(f"({x:.3f},{y:.3f})"forx,yinpts)out=[]ifshow_hidden:out+=[rf"\draw[hidden] {path(line)};"forlineinhidden]out+=[rf"\draw[visible] {path(line)};"forlineinvisible]return"\n".join(out)