1"""Figure 1.1: the same ten grains with and without coplanar triangle edges."""
2from pathlib
import Path
3from collections
import defaultdict
4from itertools
import combinations
8import matplotlib.pyplot
as plt
9from mpl_toolkits.mplot3d.art3d
import Poly3DCollection, Line3DCollection
10root=Path(__file__).resolve().parents[1]
11source=root.parent/
'Library/Meshes/polycrystalCube_10_periodicX.msh'
12s=source.read_text().splitlines();k=s.index(
'$Nodes');n=int(s[k+1])
13points={int(t[0]):np.array(t[1:4],float)
for t
in (x.split()
for x
in s[k+2:k+2+n])}
14k=s.index(
'$Elements');grains=defaultdict(list)
15for line
in s[k+2:k+2+int(s[k+1])]:
16 a=list(map(int,line.split()))
17 if a[1]
in (4,11):grains[a[3]].append(a[3+a[2]:3+a[2]+4])
19allp=np.array(list(points.values()));center=(allp.max(0)+allp.min(0))/2;scale=np.max(np.ptp(allp,axis=0))
20points={k:(p-center)/scale
for k,p
in points.items()}
21plt.rcParams.update({
'font.family':
'DejaVu Sans',
'font.size':11,
'pdf.fonttype':42})
22fig=plt.figure(figsize=(10,4.8));colors=plt.get_cmap(
'tab10')
23for panel,title
in enumerate([
'Simplicial mesh: volume triangulation',
'Polyhedral mesh: grains and grain boundaries']):
24 ax=fig.add_subplot(1,2,panel+1,projection=
'3d')
25 polygons=[]; facecolors=[]
26 for j,(gid,tets)
in enumerate(sorted(grains.items())):
27 faces=defaultdict(int)
29 for f
in combinations(tet,3):faces[tuple(sorted(f))]+=1
30 boundary=[f
for f,count
in faces.items()
if count==1]
31 ids=set(v
for f
in boundary
for v
in f)
32 shift=.22*np.mean([points[v]
for v
in ids],axis=0)
33 tris=np.array([[points[v]+shift
for v
in f]
for f
in boundary])
36 polygons.extend(tris);facecolors.extend([color]*len(tris))
38 planes=defaultdict(list)
40 p=np.array([points[v]
for v
in f]);normal=np.cross(p[1]-p[0],p[2]-p[0]);normal/=np.linalg.norm(normal)
41 if normal[np.argmax(np.abs(normal))]<0:normal=-normal
42 key=tuple(np.round(np.r_[normal,np.dot(normal,p[0])],6));planes[key].append(f)
43 for fs
in planes.values():
44 counts=defaultdict(int)
46 for e
in combinations(f,2):counts[tuple(sorted(e))]+=1
47 adjacency=defaultdict(list)
48 for (a,b),count
in counts.items():
49 if count==1:adjacency[a].append(b);adjacency[b].append(a)
50 assert all(len(v)==2
for v
in adjacency.values())
51 first=next(iter(adjacency));loop=[first];prev=
None;cur=first
53 nxt=next(v
for v
in adjacency[cur]
if v!=prev)
55 loop.append(nxt);prev,cur=cur,nxt
56 assert len(loop)==len(adjacency)
57 polygons.append([points[v]+shift
for v
in loop]);facecolors.append(color)
58 ax.add_collection3d(Poly3DCollection(polygons,facecolors=facecolors,edgecolors=
'#344450',linewidths=.24
if panel==0
else .65,alpha=1,zsort=
'average'))
59 ax.set(xlim=(-.67,.67),ylim=(-.67,.67),zlim=(-.67,.67),title=title)
60 ax.set_box_aspect((1,1,1),zoom=1.5);ax.view_init(22,-55);ax.set_proj_type(
'ortho');ax.set_axis_off()
61fig.subplots_adjust(left=0,right=1,top=.9,bottom=.12,wspace=.02)
62fig.text(.5,.055,
'10 grains • Matching colors identify the same grain • Grains separated for visibility',ha=
'center',fontsize=10,color=
'#203547')
63for ext
in [
'pdf',
'png']:fig.savefig(root/
'figures'/(
'polycrystal_mesh_views.'+ext),dpi=180,bbox_inches=
'tight')
64print(
'Rendered',len(grains),
'grains and',sum(map(len,grains.values())),
'tetrahedra from',source.name)