From d42cd09c56d47a0a32d663a5ee1337bb168f4291 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8A=BD=E5=8E=9F=E5=9F=BA?= <11355381+bud-primordium@user.noreply.gitee.com> Date: Thu, 8 Feb 2024 14:09:18 +0000 Subject: [PATCH 1/5] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20devi?= =?UTF-8?q?ce=5Fimage.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- device_image.py | 95 ------------------------------------------------- 1 file changed, 95 deletions(-) delete mode 100644 device_image.py diff --git a/device_image.py b/device_image.py deleted file mode 100644 index c32475a..0000000 --- a/device_image.py +++ /dev/null @@ -1,95 +0,0 @@ -import numpy as np -import matplotlib.pyplot as plt - -def generate_device(sin_val): - num=10#线上取点数量 - n=1 - f=1 - f1=f - f2=0.2*f1 - d=0.5 - # 计算入射角 theta - theta = np.arcsin(sin_val) - D=2*f*np.tan(theta) - w1=1.5#光线宽 - w2=2*w1#透镜尺寸 - l=0.5 - fz=12#字体大小 - - def ly(x1,x2,y1,y2): - x=np.linspace(x1,x2,num) - y=(y2-y1)/(x2-x1)*(x-x1)+y1 - return y - - def lx(x1,x2): - return np.linspace(x1,x2,num) - fig,ax=plt.subplots() - - #蓝色光线两根 - ax.plot(lx(0,f1),ly(0,f1,0.5*d,0.5*D+0.5*d),'b-',linewidth=w1) - ax.plot(lx(f1,2*f1+f2),ly(f1,2*f1,0.5*D+0.5*d,0.5*D+0.5*d),'b-',linewidth=w1) - ax.plot(lx(2*f1+f2,2*f1+2*f2),ly(2*f1+f2,2*f1+2*f2,0.5*D+0.5*d,-f2/f1*0.5*d),'b-',linewidth=w1) - - ax.plot(lx(0,f1),ly(0,f1,-0.5*d,0.5*D-0.5*d),'b-',linewidth=w1) - ax.plot(lx(f1,2*f1+f2),ly(f1,2*f1,0.5*D-0.5*d,0.5*D+0.5*d+f2/f1*d),'b-',linewidth=w1) - ax.plot(lx(2*f1+f2,2*f1+2*f2),ly(2*f1+f2,2*f1+2*f2,0.5*D+0.5*d+f2/f1*d,f2/f1*0.5*d),'b-',linewidth=w1) - - #黑色光线两根 - ax.plot(lx(0,f1),ly(0,f1,0.5*d,0.5*d),'k-',linewidth=w1) - ax.plot(lx(f1,2*f1+f2),ly(f1,2*f1,0.5*d,-f2/f1*0.5*d),'k-',linewidth=w1) - ax.plot(lx(2*f1+f2,2*f1+2*f2),ly(2*f1+f2,2*f1+2*f2,-f2/f1*0.5*d,-f2/f1*0.5*d),'k-',linewidth=w1) - - ax.plot(lx(0,f1),ly(0,f1,-0.5*d,-0.5*d),'k-',linewidth=w1) - ax.plot(lx(f1,2*f1+f2),ly(f1,2*f1,-0.5*d,f2/f1*0.5*d),'k-',linewidth=w1) - ax.plot(lx(2*f1+f2,2*f1+2*f2),ly(2*f1+f2,2*f1+2*f2,f2/f1*0.5*d,f2/f1*0.5*d),'k-',linewidth=w1) - - #红 - ax.plot(lx(0,f1),ly(0,f1,-0.5*d,-0.5*D-0.5*d),'r-',linewidth=w1) - ax.plot(lx(f1,2*f1+f2),ly(f1,2*f1,-0.5*D-0.5*d,-0.5*D-0.5*d),'r-',linewidth=w1) - ax.plot(lx(2*f1+f2,2*f1+2*f2),ly(2*f1+f2,2*f1+2*f2,-0.5*D-0.5*d,f2/f1*0.5*d),'r-',linewidth=w1) - - ax.plot(lx(0,f1),ly(0,f1,0.5*d,-0.5*D+0.5*d),'r-',linewidth=w1) - ax.plot(lx(f1,2*f1+f2),ly(f1,2*f1,-0.5*D+0.5*d,-0.5*D-0.5*d-f2/f1*d),'r-',linewidth=w1) - ax.plot(lx(2*f1+f2,2*f1+2*f2),ly(2*f1+f2,2*f1+2*f2,-0.5*D-0.5*d-f2/f1*d,-f2/f1*0.5*d),'r-',linewidth=w1) - - #透镜f1 线+箭头 - ax.plot(np.linspace(f1,f1,num),np.linspace(0.5*D+0.5*d+f2/f1*d,-(0.5*D+0.5*d+f2/f1*d),num),'k-',linewidth=w2) - ax.plot(np.linspace(0.98*f1,f1,num),np.linspace(0.95*(0.5*D+0.5*d+f2/f1*d),0.5*D+0.5*d+f2/f1*d,num),'k-',linewidth=w2) - ax.plot(np.linspace(f1,1.02*f1,num),np.linspace((0.5*D+0.5*d+f2/f1*d),0.95*(0.5*D+0.5*d+f2/f1*d),num),'k-',linewidth=w2) - ax.plot(np.linspace(0.98*f1,f1,num),np.linspace(-0.95*(0.5*D+0.5*d+f2/f1*d),-(0.5*D+0.5*d+f2/f1*d),num),'k-',linewidth=w2) - ax.plot(np.linspace(f1,1.02*f1,num),np.linspace(-(0.5*D+0.5*d+f2/f1*d),-0.95*(0.5*D+0.5*d+f2/f1*d),num),'k-',linewidth=w2) - - #焦平面 - ax.plot(np.linspace(2*f1,2*f1,num),np.linspace(0.5*D+0.5*d+f2/f1*d,-(0.5*D+0.5*d+f2/f1*d),num),'k--',linewidth=w1) - #透镜f2 - ax.plot(np.linspace(2*f1+f2,2*f1+f2,num),np.linspace(0.5*D+0.5*d+f2/f1*d,-(0.5*D+0.5*d+f2/f1*d),num),'k-',linewidth=w2) - ax.plot(np.linspace((1.98*f1+f2),2*f1+f2,num),np.linspace(-0.95*(0.5*D+0.5*d+f2/f1*d),-(0.5*D+0.5*d+f2/f1*d),num),'k-',linewidth=w2) - ax.plot(np.linspace((2.02*f1+f2),2*f1+f2,num),np.linspace(-0.95*(0.5*D+0.5*d+f2/f1*d),-(0.5*D+0.5*d+f2/f1*d),num),'k-',linewidth=w2) - ax.plot(np.linspace((1.98*f1+f2),2*f1+f2,num),np.linspace(0.95*(0.5*D+0.5*d+f2/f1*d),0.5*D+0.5*d+f2/f1*d,num),'k-',linewidth=w2) - ax.plot(np.linspace((2.02*f1+f2),2*f1+f2,num),np.linspace(0.95*(0.5*D+0.5*d+f2/f1*d),(0.5*D+0.5*d+f2/f1*d),num),'k-',linewidth=w2) - - #掩膜 - ax.plot(np.linspace(0,0,num),np.linspace(-0.5*d,0.5*d,num),'k--',linewidth=w2) - #像 - ax.plot(np.linspace(2*f1+2*f2,2*f1+2*f2,num),np.linspace(-0.5*f2/f1*d,0.5*f2/f1*d,num),'k--',linewidth=1.5*w1) - #光源部分 - ax.plot(np.linspace(-2*l,-l,num),np.linspace(0,0.5*d,num),'purple',linewidth=w1) - ax.plot(np.linspace(-2*l,-l,num),np.linspace(0,-0.5*d,num),'purple',linewidth=w1) - ax.plot(np.linspace(-l,-l,num),np.linspace(-0.5*d,0.5*d,num),'k-',linewidth=w2) - ax.plot(np.linspace(-1.02*l,-l,num),np.linspace(-0.4*d,-0.5*d,num),'k-',linewidth=w2) - ax.plot(np.linspace(-0.98*l,-l,num),np.linspace(-0.4*d,-0.5*d,num),'k-',linewidth=w2) - ax.plot(np.linspace(-1.02*l,-l,num),np.linspace(0.4*d,0.5*d,num),'k-',linewidth=w2) - ax.plot(np.linspace(-0.98*l,-l,num),np.linspace(0.4*d,0.5*d,num),'k-',linewidth=w2) - ax.plot(np.linspace(-l,0,num),np.linspace(0.5*d,0.5*d,num),'purple',linewidth=w1) - ax.plot(np.linspace(-l,0,num),np.linspace(-0.5*d,-0.5*d,num),'purple',linewidth=w1) - #文字说明 - ax.text(-l,-D/2,'light source',ha='center',va='center',fontsize=fz) - ax.text(0,-D/2,'mask',ha='center',va='center',fontsize=fz) - ax.text(0.5*f1,-D/2,'f1',ha='center',va='center',fontsize=fz) - ax.text(1.5*f1,-D/2,'f1',ha='center',va='center',fontsize=fz) - ax.text(2*f1,-D/2,'focal plane',ha='center',va='center',fontsize=fz) - ax.text(2*f1+0.8*f2,-D/2,'f2',ha='center',va='center',fontsize=fz) - ax.text(2*f1+1.5*f2,-D/2,'f2',ha='center',va='center',fontsize=fz) - ax.text(2*f1+2*f2,-D/2,'image',ha='center',va='center',fontsize=fz) - - return fig # 返回 Matplotlib 图形容器 \ No newline at end of file -- Gitee From b02792beb2cdcad9a6b69df769d8c31aec3d671a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8A=BD=E5=8E=9F=E5=9F=BA?= <11355381+bud-primordium@user.noreply.gitee.com> Date: Thu, 8 Feb 2024 14:11:29 +0000 Subject: [PATCH 2/5] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20plot?= =?UTF-8?q?=5Fimage.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plot_image.py | 118 -------------------------------------------------- 1 file changed, 118 deletions(-) delete mode 100644 plot_image.py diff --git a/plot_image.py b/plot_image.py deleted file mode 100644 index 6c3c091..0000000 --- a/plot_image.py +++ /dev/null @@ -1,118 +0,0 @@ -import numpy as np -import os -import pkg_resources -import matplotlib.pyplot as plt -from numpy.fft import fftshift, fft2, ifftshift, fftfreq, ifft2 - -#正逆问题打包绘图函数generate_plot -#输出一个两行五列图像,第一行mask,第二行光强分布/图像边界 -#传入以下函数参数 -#sin_val = 0.9 # 传入的 sin 值 -#n_val = 1 # 传入的折射率值 -#wavelength_val = 2.0 # 传入的波长值 -#m = 0 # 传入的 m 值,0为相干光 1为非相干光 -#b = 0 # 传入的 b 值,0为不启用OPC,1为启用OPC -#op_status = 0 # 0表示第二行输出光强分布,1表示输出图像边界 -def generate_plot(sin_val, n_val, wavelength_val, m, b, op_status): - # 固定参数 - d = f = 1.0 - - # 计算入射角 theta - theta = np.arcsin(sin_val) - - # 计算数值孔径 - NA = n_val * np.sin(theta) - - # 波长 - wavelength = wavelength_val - - # 计算透镜孔径 D - D = 2 * f * np.tan(theta) - - # 实空间坐标架、动量空间坐标架及光学传递函数 - N = 100 # 一个维度的采样数 - p=1#修正离散化导致的误差 - Nb=(2*p+1)*N - x = np.linspace(-0.6 * d, 0.6 * d, N) - y = x.copy() - X, Y = np.meshgrid(x, y) - Y = Y[::-1, :] - kx = fftshift(fftfreq(Nb,1.2*d*(2*p+1)/Nb)) - ky = kx.copy() - Kx, Ky = np.meshgrid(kx, ky) - k = np.sqrt(Kx**2 + Ky**2) - K = 2 * np.pi * NA / wavelength - OTF = np.zeros((2, Nb, Nb)) - OTF[0][0:Nb][0:Nb] = k <= K - for i in range(Nb): - for j in range(Nb): - if k[i][j] < K: - OTF[1][i][j] = np.arccos(k[i][j] / 2 / K) - k[i][j] / 2 / K * np.sqrt(1 - (k[i][j] / 2 / K)**2) - - # 加载文件 - file_path = pkg_resources.resource_filename(__name__, "mask.npy") - amask = np.load(file_path) - bmask=np.zeros((Nb,Nb)) - - def image(mask): - bmask[p*N:(p+1)*N:1,p*N:(p+1)*N:1]=mask - diffracted = fftshift(fft2(ifftshift(bmask))) - transmitted=diffracted*OTF[m] - E=fftshift(ifft2(ifftshift(transmitted))) - intensity=np.abs(E)**2 - output=np.zeros((N,N)) - output=intensity[p*N:(p+1)*N,p*N:(p+1)*N] - return output/output.max() - - - def PE(T, tv, mask): - mp = np.zeros((N, N)) - mp[image(T) > tv] = 1 - return np.sum(np.abs(mp - mask).reshape(N**2, 1)) - - - def contour(T): - res = 1 - i = 0 - temp = N**2 - while i <= 35: - t = 0.4 - 0.01 * i - if PE(T, t, T) <= temp: - temp = PE(T, t, T) - res = t - i = i + 1 - Im = image(T) - edge = np.zeros((N, N)) - edge[Im > res] = 1 - return edge - - - # 创建 Matplotlib 图形容器 - fig, axs = plt.subplots(2, 5, figsize=(12, 8)) - - # 第一行:FUDAN五个字母对应的掩膜版 - for i in range(5): - axs[0, i].imshow(amask[i + 5 * b], cmap='gray', extent=(x.min(), x.max(), y.min(), y.max())) - axs[0, i].set_title(f'Mask {i}') - - # 第二行:光强分布或者图像边界,根据op_status决定 - for i in range(5): - if op_status == 0: - intensity = image(amask[i + 5 * b]) - axs[1, i].imshow(intensity, cmap='viridis', extent=(x.min(), x.max(), y.min(), y.max())) - axs[1, i].set_title(f'Intensity {i}') - else: - axs[1, i].imshow(contour(amask[i + 5 * b]), cmap='gray', extent=(x.min(), x.max(), y.min(), y.max())) - axs[1, i].set_title(f'Contour {i}') - - # 调整子图布局 - plt.tight_layout() - - return fig # 返回 Matplotlib 图形容器 - -# 绘图函数调用示例 -plotter = generate_plot(0.9, 1, 2.0, 1, 0, 1) -#plt.show() - - - -- Gitee From 2e5ad8eba082246d660d60be812963af831b4911 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8A=BD=E5=8E=9F=E5=9F=BA?= <11355381+bud-primordium@user.noreply.gitee.com> Date: Thu, 8 Feb 2024 14:15:23 +0000 Subject: [PATCH 3/5] =?UTF-8?q?=E4=B8=89=E7=A8=BF=EF=BC=8C=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E6=B3=A8=E9=87=8A=EF=BC=8C=E4=BC=98=E5=8C=96=E5=B8=83?= =?UTF-8?q?=E5=B1=80=EF=BC=8C=E6=B7=BB=E5=8A=A0=E5=B8=AE=E5=8A=A9=E8=AF=B4?= =?UTF-8?q?=E6=98=8E=E7=AA=97=E5=8F=A3=E3=80=82=E4=BF=AE=E6=AD=A3=E5=B0=BA?= =?UTF-8?q?=E5=AF=B8=E3=80=81=E7=AA=97=E5=8F=A3=E5=AE=9A=E4=BD=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 芽原基 <11355381+bud-primordium@user.noreply.gitee.com> --- README.en.md | 130 +++++++++++++++++++++--------- README.md | 130 +++++++++++++++++++++--------- device.py | 221 ++++++++++++++++++++++++++++++++------------------- help.py | 47 +++++++++++ main.py | 148 +++++++++++++++++++++++++++------- 5 files changed, 491 insertions(+), 185 deletions(-) create mode 100644 help.py diff --git a/README.en.md b/README.en.md index ed40d77..a6a2114 100644 --- a/README.en.md +++ b/README.en.md @@ -1,40 +1,94 @@ # Fudan 2023 Optical Honor Course - Lithography -#### Introduction -This is a Python program for the lithography project in Professor Shi's Optical Honor Course of the Fudan University Physics Department in 2023. The project is divided into direct and inverse problems, integrated into a Python application, aiming to assist in the teaching of fundamental lithography knowledge. - -#### Software Architecture -1. **main.py:** The main program utilizes Tkinter to create a Tk window for GUI visualization. It consists of four areas: "Device Illustration Area," "Mask Display Area," "Result Calculation Area," and "Control Area." -2. **mask.npy:** Contains 10 100x100 NumPy arrays. The first five are binary masks (mask) for F/U/D/A/N normal lithography, and the last five are masks with OPC (Optical Proximity Correction). -3. **device.py:** Custom module for drawing the "Device Illustration Area." -4. **mask.py:** Custom module for drawing the "Mask Display Area." -5. **result.py:** Custom module for drawing the "Result Calculation Area." - -#### Installation Guide -Windows users can find the executable (.exe) release. For other platforms, please compile from the source code. - -#### User Guide -The simulation is based on a low-pass filtered 4F system. The control area allows adjustments to "sinθ," "n," and "λ," representing the aperture size, refractive index, and wavelength, where the first two jointly determine the numerical aperture (N.A.). - -Three buttons control "Coherent/Incoherent," "Enable OPC," and "Display Intensity/Boundary": -1. Enabling OPC locks the sliders to specific values, unlocking them upon disabling OPC. -2. When the button displays "Intensity," the "Result Calculation Area" shows the intensity distribution on the image plane; otherwise, it shows a binary boundary recognition image. -3. When the wavelength is too long, resulting in a uniform intensity distribution, a purple region may appear, and the boundary recognition may be inaccurate. In such cases, reducing the wavelength is recommended. - -#### Brief Explanation of OPC -The goal is to minimize the variance of the intensity distribution between the target pattern and the projected pattern. We optimize based on pixel values of the mask, using two gradient-free optimization algorithms: a binary genetic algorithm (using the Deap library) and a variable substitution using CMA-ES. The final mask is determined by selecting the better result between the two methods. Optimized masks show a more uniform intensity distribution and edges closer to the expected pattern. - -#### Contributors -Supervisors: Professor Lei Shi, Professor A Guang -Project Contributors: XinHui Guo, JinYuan Huo, YuZhang Wu, YuanQing Yang -Copyright belongs to the Physics Department, Fudan University. - -#### Special Features -1. GUI visualization interactive interface. -2. Boundary recognition based on a genetic algorithm. -3. Function encapsulation and custom modules for code maintainability. -4. Distribution of .exe files using pyinstaller. -5. Synchronized updates of three graphical areas with control area adjustments. -6. Built-in NumPy binary files, optimized functions, and minimized memory footprint. - -Feel free to contribute and improve the project! +## Introduction + +The 2023 Optical Honor Course at the Physics Department of Fudan University, led by Professor Shi, is divided into two parts: the forward problem and the inverse problem, integrated through a Python small program. The aim is to assist in teaching the fundamental concepts related to lithography. + +## Software Architecture + +1. `main.py`: The main program utilizes Tkinter to create a GUI visualization interface, comprising four areas: "Device Schematic Area," "Mask Display Area," "Calculation Result Area," and "Control Area." + - Device Schematic Area: Displays the optical system simulated by this program. + - Mask Display Area: Shows the currently used mask, which changes when OPC is enabled. + - Calculation Result Area: Displays the intensity distribution/boundary recognition on the image plane according to the specified output mode. + - Control Area: Users can adjust simulation parameters and output modes. +2. `mask.npy`: Contains 10 numpy arrays of size 100*100. The first five are F/U/D/A/N normal binary masks, and the latter five are OPC-corrected masks. +3. `device.py`: Custom module for drawing the "Device Schematic Area." +4. `mask.py`: Custom module for drawing the "Mask Display Area." +5. `result.py`: Custom module for drawing the "Calculation Result Area." +6. `help.py`: Custom module for drawing the help window, assisting in rendering this document. +7. `README.(en.)md`: This help document.' + +## Installation Guide + +An .exe distribution for the Windows platform is provided. Note that `main.py` binds the WM_DELETE_WINDOW event and utilizes the `screeninfo` library to obtain the actual size of the display. For other platforms, please adapt and modify the source code accordingly. + +## User Instructions + +This program mainly simulates a low-pass filtering 4F system. The control area allows adjustment of "sinθ," "n," and "λ," representing the aperture size, immersion medium refractive index, and wavelength, respectively. The first two jointly determine the numerical aperture N.A. +Button functionalities: + +- "Coherent/Incoherent": Controls coherent and incoherent light sources. +- "Enable OPC": Enables/disables OPC correction. +- "Display Intensity/Boundary": Switches result display between intensity distribution and boundary recognition. +Notes: + +1. When OPC is enabled, the sliders will be locked to the specified values. They can be unlocked by disabling OPC (clicking again). +2. When the button displays "Intensity," the "Calculation Result Area" shows the intensity distribution on the image plane. Otherwise, it shows the boundary recognition image. +3. When the wavelength is too long, purple areas and inaccurate boundary recognition may occur. It is recommended to reduce the wavelength. + +## Introduction to Optical Proximity Correction (OPC) + +Optical Proximity Correction (OPC) is a technique used to address pattern offset and shape distortion issues in lithography. It involves local modifications to the lithography mask to correct variations in pattern shape and size caused by optical limitations, thereby improving the accuracy and reliability of chip manufacturing. + +### Background + +As integrated circuit designs continue to evolve, chip feature sizes are becoming smaller. However, the resolution achievable by lithography is limited by optical physics, leading to problems such as pattern distortion, size variation, and interference between patterns. + +### Objective + +The primary goal of OPC is to correct variations in pattern shape and size caused by optical limitations by locally modifying the mask, thereby improving the accuracy and reliability of chip manufacturing. + +### Working Principle + +OPC works by introducing a series of minor modifications to the lithography mask, ensuring that the lithographic patterns on the chip maintain their intended shape and size during exposure. These minor modifications are typically implemented through software algorithms and can be optimized based on specific process requirements and lithography machine characteristics. + +### Types + +OPC is generally divided into two types: rule-based OPC and model-based OPC. Rule-based OPC relies on a series of predefined rules and empirical laws for correction and is suitable for simple processes, while model-based OPC relies on physical models and simulations for correction, allowing for more precise adjustments to pattern shape and size. + +### Correction Algorithm Used in this Program + +Targeting the minimization of variance in the intensity distribution between the target pattern and the projected pattern, we optimize based on the pixels of the mask. We employ two gradient-free optimization algorithms: + +1. Setting the amplitude transmittance of each pixel on the mask to 0 or 1, using a genetic algorithm (Deap library) to iterate 10,000 generations to obtain the optimized mask. +2. Substituting the amplitude transmittance of each pixel with 0.5*(1+cos(t)), optimizing using CMA-ES (Covariance Matrix Adaptation Evolution Strategy) for 10,000 iterations, setting the pixels with amplitude transmittance greater than 0.5 on the resulting mask to 1, and the rest to 0, obtaining a mask that conforms to reality. + +The better-performing mask of the two is finally adopted. It can be observed that the intensity distribution of the optimized mask's projected pattern is more uniform, and the edges are closer to the expected pattern. It is important to note that the algorithms used in this program are only introductory examples. For more information, please refer to [Lithography Guru](https://www.lithoguru.com/). + +## Contribution + +- Supervising Teachers: Professor Shi Lei, Professor A Guang +- Project Participants: Guo Xinhui, Huo Jinyuan, Wu Yuzhang, Yang Yuanqing +- Process handling method when closing the window is from [Zheng Zepeng](https://gitee.com/zhengzepeng668/honors-in-optics) +- Inspiration for drawing colored intensity distribution graphs from [Xie Yuncheng](https://gitee.com/xie-yuncheng/xyc) +- Fourier method reference from the repository recommended by [Wu Yuhang](https://github.com/rafael-fuente/diffractsim/) +- Copyright belongs to the Physics Department of Fudan University. + +## Special Features + +1. GUI visualization interactive interface +2. Binary boundary recognition based on genetic algorithm +3. Function encapsulation, custom modules for enhanced code maintainability +4. Distribution of .exe files using pyinstaller +5. Synchronization of the three major image areas with adjustments in the control area +6. Built-in numpy binary files, extensive optimization of function functions, minimizing memory usage +7. Embedded bilingual help documentation based on Markdown + +## References + +1. [Hecht E. Optics[M]. 5th edition. Pearson, 2015.](https://www.pearson.com/en-us/subject-catalog/p/optics/P200000006793) +2. [Ma X, Zhang S, Pan Y, et al. Research and Progress in Computational Lithography. Laser & Optoelectronics Progress, 2022, 59(9): 122-170.](https://doi.org/10.3788/LOP202259.0922008) + +## Project Repository Address + +You can visit the [repository of this project on Gitee](https://gitee.com/bud-primordium/lithography) and show your support by giving it a star~ diff --git a/README.md b/README.md index 176165f..ab9c42c 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,95 @@ # 复旦2023光学荣誉课-光刻 -#### 介绍 -2023复旦大学物理学系石老师光学荣誉课——光刻project,分为正问题与逆问题,集成为python小程序,旨在协助光刻机相关基本知识教学 - -#### 软件架构 -1. main.py 主程序,借助tkinter创建Tk窗口进行GUI可视化操作,分为四个区域:“装置示意图区”、“掩膜版展示区”、“计算结果区”与“控制区” -2. mask.npy 内置10个100*100的numpy数组,前五个为F/U/D/A/N正常二值化掩膜版(mask),后五个为基于OPC修正的mask -3. device.py 自定义模块,绘制“装置示意图区” -4. mask.py 自定义模块,绘制“掩膜版展示区” -5. result.py 自定义模块,绘制“计算结果区” - -#### 安装教程 -已有Windows平台下的.exe发行版,其余平台请根据源码自行编译 - -#### 使用说明 -模拟主体为低通滤波的4F系统 -控制区可调节“sinθ”、“n”、“λ”,表征光阑大小、介质折射能力与光波波长,前两者共同决定数值孔径N.A. -三个按钮分别控制:“相干/非相干”、“是否启用OPC”以及“展示光强/边界” -1. 启用OPC后,因为掩膜版优化是基于特定条件的,所以上方滑条均被锁定为指定值,取消启用OPC(再次点击)后即可解锁 -2. 当按钮显示“光强”时,“计算结果区”即展示像平面上的光强分布,反之则为二值化的边界识别图 -3. 当波长过长,亦即像平面光强相对分布过于均匀时,将出现一片紫色,边界也无法正确识别,此时建议减小波长 - -#### OPC简要解释 -以最小化目标图案与投影图案的光强分布方差为目标,我们基于掩模板的像素进行优化,采用了两种无剃度优化算法:其一是设置掩模板上每个像素的振幅透过率为0或1,采用遗传算法(Deap库),迭代10000代后得到优化后的掩模板;其二是将每个像素的振幅透过率进行变量代换为0.5*(1+cos(t)),利用CMA-ES(协方差矩阵自适应演化算法)进行优化,将迭代10000次后得到的掩模板上振幅透过率大于0.5的像素块设置为1,其余则为0,得到符合实际的掩模板。最后采用两者中效果更佳者。容易观察到,优化后的掩模板的投影图案光强分布更均匀,且边缘也更接近所期望的图案。 - -#### 参与贡献 -指导老师:石磊老师 阿光老师 -项目完成人:郭新会 霍晋元 吴昱璋 杨远青 -版权归复旦大学物理学系所有 - -#### 特技 - -1. GUI可视化互动界面 -2. 基于遗传算法的二值化边界识别 -3. 函数封装、自定义模块使得代码可维护性较强 -4. 借助pyinstaller发行.exe -5. 三大图像区同步随控制区调节 -6. 内置numpy二进制文件,功能函数大量优化,最大限度减少内存占用 +## 介绍 + +2023复旦大学物理学系石老师光学荣誉课——光刻project,分为正问题与逆问题两部分,通过Python小程序进行集成,旨在辅助计算光刻相关基础知识的教学。 + +## 软件架构 + +1. `main.py`:主程序,利用Tkinter创建GUI可视化操作界面,包含四个区域:“装置示意图区”、“掩膜版展示区”、“计算结果区”与“控制区” + - 装置示意图区:显示本程序模拟的光学系统 + - 掩膜版展示区:显示当前使用的掩膜版(mask),启用OPC后会改变 + - 计算结果区:根据指定的输出模式,显示像平面上的光强分布/边界识别 + - 控制区:用户可以调节模拟参数与输出模式 +2. `mask.npy`:包含10个100*100的numpy数组,前五个为F/U/D/A/N正常二值化掩膜版(mask),后五个为基于OPC修正的mask +3. `device.py`:自定义模块,用于绘制“装置示意图区” +4. `mask.py`:自定义模块,用于绘制“掩膜版展示区” +5. `result.py`:自定义模块,用于绘制“计算结果区” +6. `help.py`:自定义模块,用于绘制帮助窗口,辅助渲染本文档并嵌入主程序 +7. `README.(en.)md`:本帮助文档 + +## 安装教程 + +已提供Windows平台下的.exe发行版,注意`main.py`中绑定了WM_DELETE_WINDOW事件,并使用了`screeninfo`库获取显示器真实大小,其他平台请根据源码自行适配修改。 + +## 使用说明 + +本程序模拟主体为低通滤波的4F系统。控制区可调节“sinθ”、“n”、“λ”,表征光阑大小、浸没介质折射能力与光波波长,前两者共同决定数值孔径N.A. +按钮功能: + +- “相干/非相干”:控制相干与非相干光源 +- “是否启用OPC”:启用/取消OPC修正 +- “展示光强/边界”:切换结果展示,显示光强分布或边界识别图 +注意事项: + +1. 启用OPC后,滑条将被锁定为指定值,取消启用OPC(再次点击)后可解锁 +2. 当按钮显示“光强”时,“计算结果区”展示像平面上的光强分布,反之则为边界识别图 +3. 波长过长时,可能出现紫色区域及边界识别不准确,建议减小波长。 + +## 邻近效应纠正(OPC)简介 + +邻近效应修正(OPC)是一种解决光刻技术中图案偏移和形状畸变问题的技术。它通过对光刻掩模进行局部修正,校正光学限制导致的图案形状和尺寸的变化,以提高芯片制造的精度和可靠性。 + +### 背景 + +随着集成电路设计的不断发展,芯片的特征尺寸越来越小,而光刻技术所能达到的分辨率却受到光学物理学的限制,无法完全满足制程的需求。这就导致了光刻图案的形状失真、尺寸变化和图案间的干涉等问题。 + +### 目的 + +OPC的主要目的是通过对掩模进行局部修正,以校正因光学限制而导致的图案形状和尺寸的变化,从而提高芯片制造的精度和可靠性。 + +### 工作原理 + +OPC的工作原理是在光刻掩模上引入一系列微小的修正,使得芯片上的光刻图案在曝光时能够更好地保持设计时的形状和尺寸。这些微小的修正通常通过软件算法来实现,可以根据具体的制程要求和光刻机的特性进行优化。 + +### 类型 + +OPC通常分为规则OPC(Rule-based OPC)和模型OPC(Model-based OPC)两种类型。规则OPC基于一系列预定义的规则和经验法则来进行修正,适用于简单的制程;而模型OPC则基于物理模型和仿真来进行修正,可以更精细地校正图案形状和尺寸。 + +### 本程序采用的修正算法 + +以最小化目标图案与投影图案的光强分布方差为目标,我们基于掩模板的像素进行优化,采用了两种无剃度优化算法: + +1. 设置掩模板上每个像素的振幅透过率为0或1,采用遗传算法(Deap库),迭代10000代后得到优化后的掩模板; + +2. 将每个像素的振幅透过率进行变量代换为0.5*(1+cos(t)),利用CMA-ES(协方差矩阵自适应演化算法)进行优化,将迭代10000次后得到的掩模板上振幅透过率大于0.5的像素块设置为1,其余则为0,得到符合实际的掩模板。 + +最后采用两者中效果更佳者。容易观察到,优化后的掩模板的投影图案光强分布更均匀,且边缘也更接近所期望的图案。需要注意的是,本程序采用的算法仅为入门版示例,更多知识请查阅[Lithography Guru](https://www.lithoguru.com/). + +## 参与贡献 + +- 指导老师:石磊老师、阿光老师 +- 项目完成人:郭新会、霍晋元、吴昱璋、杨远青 +- 关闭窗口时的进程处理方法来自[郑泽鹏同学](https://gitee.com/zhengzepeng668/honors-in-optics) +- 光强分布图彩图绘制灵感来自[谢昀城同学](https://gitee.com/xie-yuncheng/xyc) +- 角谱法借鉴了[吴宇航同学推荐的仓库](https://github.com/rafael-fuente/diffractsim/) +- 版权归复旦大学物理学系所有 + +## 特技 + +1. GUI可视化互动界面 +2. 基于遗传算法的二值化边界识别 +3. 函数封装、自定义模块使得代码可维护性较强 +4. 借助pyinstaller发行.exe +5. 三大图像区同步随控制区调节 +6. 内置numpy二进制文件,功能函数大量优化,最大限度减少内存占用 +7. 内嵌基于Markdown的中英双语帮助文档 + +## 参考文献 + +1. [Hecht E. Optics[M]. 5th edition. Pearson, 2015.](https://www.pearson.com/en-us/subject-catalog/p/optics/P200000006793) +2. [马旭, 张胜恩, 潘毅华, 等. 计算光刻研究及进展. 激光与光电子学进展, 2022, 59(9): 122-170.](https://doi.org/10.3788/LOP202259.0922008) + +## 项目仓库地址 + +您可以在[Gitee上访问本项目的仓库](https://gitee.com/bud-primordium/lithography),可以随手支持,Star一下~ diff --git a/device.py b/device.py index a800c26..0980732 100644 --- a/device.py +++ b/device.py @@ -1,89 +1,101 @@ # device.py -def draw_static_elements(device_canvas): +import tkinter as tk + +def draw_static_elements(canvas): + # 设置字体 + font_en = ('Times New Roman', 12, 'italic') + font_ch = ('KaiTi', 12, ) + #绘制静态元素 - device_canvas.create_line(275, 225, 325, 225) - device_canvas.create_line(275, 275, 325, 275) - device_canvas.create_line(325, 150, 325, 225) - device_canvas.create_line(325, 275, 325, 350) - device_canvas.create_line(325, 225, 325, 275, width=3, dash=4) - device_canvas.create_oval(220, 245, 230, 255, fill='black') - device_canvas.create_line(225, 250, 275, 225) - device_canvas.create_line(225, 250, 275, 225 + 50 / 3) - device_canvas.create_line(225, 250, 275, 225 + 100 / 3) - device_canvas.create_line(225, 250, 275, 275) - device_canvas.create_oval(265, 220, 285, 280, width=3) - device_canvas.create_line(275, 150, 275, 350, dash=4) - device_canvas.create_line(275, 225 + 50 / 3, 325, 225 + 50 / 3) - device_canvas.create_line(275, 225 + 100 / 3, 325, 225 + 100 / 3) - device_canvas.create_line(320, 220 + 50 / 3, 325, 225 + 50 / 3) - device_canvas.create_line(320, 230 + 50 / 3, 325, 225 + 50 / 3) - device_canvas.create_line(320, 220 + 100 / 3, 325, 225 + 100 / 3) - device_canvas.create_line(320, 230 + 100 / 3, 325, 225 + 100 / 3) - device_canvas.create_line(320, 220, 325, 225) - device_canvas.create_line(320, 230, 325, 225) - device_canvas.create_line(320, 270, 325, 275) - device_canvas.create_line(320, 280, 325, 275) - device_canvas.create_text(225, 284, text='光源') - device_canvas.create_text(325, 360, text='掩模板') - device_canvas.create_line(325, 345, 330, 340) - device_canvas.create_line(325, 345, 330, 350) - device_canvas.create_line(425, 345, 420, 340) - device_canvas.create_line(425, 345, 420, 350) - device_canvas.create_line(425, 345, 430, 340) - device_canvas.create_line(425, 345, 430, 350) - device_canvas.create_line(525, 345, 520, 340) - device_canvas.create_line(525, 345, 520, 350) - device_canvas.create_line(525, 345, 530, 340) - device_canvas.create_line(525, 345, 530, 350) - device_canvas.create_line(625, 345, 620, 340) - device_canvas.create_line(625, 345, 620, 350) - device_canvas.create_line(625, 345, 630, 340) - device_canvas.create_line(625, 345, 630, 350) - device_canvas.create_line(725, 345, 720, 340) - device_canvas.create_line(725, 345, 720, 350) - device_canvas.create_line(325, 345, 370, 345) - device_canvas.create_line(380, 345, 425, 345) - device_canvas.create_line(425, 345, 470, 345) - device_canvas.create_line(480, 345, 525, 345) - device_canvas.create_line(525, 345, 570, 345) - device_canvas.create_line(580, 345, 625, 345) - device_canvas.create_line(625, 345, 670, 345) - device_canvas.create_line(680, 345, 725, 345) - device_canvas.create_text(375, 345, text='f') - device_canvas.create_text(475, 345, text='f') - device_canvas.create_text(575, 345, text='f') - device_canvas.create_text(675, 345, text='f') - device_canvas.create_text(525, 510, text='透镜组') - device_canvas.create_text(725, 360, text='像屏') - device_canvas.create_line(425, 0, 425, 500, dash=4) - device_canvas.create_line(625, 0, 625, 500, dash=4) - device_canvas.create_line(525, 0, 525, 500, dash=4) - device_canvas.create_line(325, 225, 425, 225) - device_canvas.create_line(325, 275, 425, 275) - device_canvas.create_line(425, 225, 625, 275) - device_canvas.create_line(425, 275, 625, 225) - device_canvas.create_line(725, 225, 625, 225) - device_canvas.create_line(725, 275, 625, 275) - device_canvas.create_line(725, 150, 725, 350) + # 注意,这一版每个元素的x坐标都减去了200 + canvas.create_line(75, 225, 125, 225) + canvas.create_line(75, 275, 125, 275) + canvas.create_line(125, 150, 125, 225) + canvas.create_line(125, 275, 125, 350) + canvas.create_line(125, 225, 125, 275, width=3, dash=4) + canvas.create_oval(20, 245, 30, 255, fill='black') + canvas.create_line(25, 250, 75, 225) + canvas.create_line(25, 250, 75, 225 + 50 / 3) + canvas.create_line(25, 250, 75, 225 + 100 / 3) + canvas.create_line(25, 250, 75, 275) + canvas.create_oval(65, 220, 85, 280, width=3) + canvas.create_line(75, 150, 75, 350, dash=4) + canvas.create_line(75, 225 + 50 / 3, 125, 225 + 50 / 3) + canvas.create_line(75, 225 + 100 / 3, 125, 225 + 100 / 3) + canvas.create_line(120, 220 + 50 / 3, 125, 225 + 50 / 3) + canvas.create_line(120, 230 + 50 / 3, 125, 225 + 50 / 3) + canvas.create_line(120, 220 + 100 / 3, 125, 225 + 100 / 3) + canvas.create_line(120, 230 + 100 / 3, 125, 225 + 100 / 3) + canvas.create_line(120, 220, 125, 225) + canvas.create_line(120, 230, 125, 225) + canvas.create_line(120, 270, 125, 275) + canvas.create_line(120, 280, 125, 275) + canvas.create_text(25, 284, text='光源', font=font_ch) + canvas.create_text(20, 304, text='波长', font=font_ch) + canvas.create_text(42, 304, text='λ', font=font_en) + canvas.create_text(125, 360, text='掩模板', font=font_ch) + canvas.create_line(125, 345, 130, 340) + canvas.create_line(125, 345, 130, 350) + canvas.create_line(225, 345, 220, 340) + canvas.create_line(225, 345, 220, 350) + canvas.create_line(225, 345, 230, 340) + canvas.create_line(225, 345, 230, 350) + canvas.create_line(325, 345, 320, 340) + canvas.create_line(325, 345, 320, 350) + canvas.create_line(325, 345, 330, 340) + canvas.create_line(325, 345, 330, 350) + canvas.create_line(425, 345, 420, 340) + canvas.create_line(425, 345, 420, 350) + canvas.create_line(425, 345, 430, 340) + canvas.create_line(425, 345, 430, 350) + canvas.create_line(525, 345, 520, 340) + canvas.create_line(525, 345, 520, 350) + canvas.create_line(125, 345, 170, 345) + canvas.create_line(180, 345, 225, 345) + canvas.create_line(225, 345, 270, 345) + canvas.create_line(280, 345, 325, 345) + canvas.create_line(325, 345, 370, 345) + canvas.create_line(380, 345, 425, 345) + canvas.create_line(425, 345, 470, 345) + canvas.create_line(480, 345, 525, 345) + canvas.create_text(175, 345, text='f', font=font_en) + canvas.create_text(275, 345, text='f', font=font_en) + canvas.create_text(375, 345, text='f', font=font_en) + canvas.create_text(475, 345, text='f', font=font_en) + canvas.create_text(325, 510, text='透镜组', font=font_ch) + canvas.create_text(525, 360, text='像屏', font=font_ch) + canvas.create_text(468, 300, text='浸没折射率', font=font_ch) + canvas.create_text(515, 300, text='n', font=font_en) + canvas.create_line(225, 0, 225, 500, dash=4) + canvas.create_line(425, 0, 425, 500, dash=4) + canvas.create_line(325, 0, 325, 500, dash=4) + canvas.create_line(125, 225, 225, 225) + canvas.create_line(125, 275, 225, 275) + canvas.create_line(225, 225, 425, 275) + canvas.create_line(225, 275, 425, 225) + canvas.create_line(525, 225, 425, 225) + canvas.create_line(525, 275, 425, 275) + canvas.create_line(525, 150, 525, 350) -def draw_dynamic_elements(device_canvas, a, m): +def draw_dynamic_elements(canvas, a, m): # 绘制动态元素 - device_canvas.create_oval(410, 225 - a, 440, 275 + a, width=3, tags="dynamic") - device_canvas.create_oval(610, 225 - a, 640, 275 + a, width=3, tags="dynamic") - device_canvas.create_line(325, 225, 425, 225 - a, tags="dynamic") - device_canvas.create_line(325, 275, 425, 275 - a, tags="dynamic") - device_canvas.create_line(325, 225, 425, 225 + a, tags="dynamic") - device_canvas.create_line(325, 275, 425, 275 + a, tags="dynamic") - device_canvas.create_line(425, 225 - a, 625, 275 - a, tags="dynamic") - device_canvas.create_line(425, 275 - a, 625, 225 - a, tags="dynamic") - device_canvas.create_line(425, 225 + a, 625, 275 + a, tags="dynamic") - device_canvas.create_line(425, 275 + a, 625, 225 + a, tags="dynamic") - device_canvas.create_line(725, 225, 625, 225 - a, tags="dynamic") - device_canvas.create_line(725, 275, 625, 275 - a, tags="dynamic") - device_canvas.create_line(725, 225, 625, 225 + a, tags="dynamic") - device_canvas.create_line(725, 275, 625, 275 + a, tags="dynamic") - device_canvas.create_line(725, 225, 725, 275, dash=4, width=3*m-0.5, tags="dynamic") + # 注意,每个元素的x坐标都减去了200,横纵再以600进行缩放为相对单位 + canvas.create_oval(210, 225 - a, 240, 275 + a, width=3, tags="dynamic") + canvas.create_oval(410, 225 - a, 440, 275 + a, width=3, tags="dynamic") + canvas.create_line(125, 225, 225, 225 - a, tags="dynamic") + canvas.create_line(125, 275, 225, 275 - a, tags="dynamic") + canvas.create_line(125, 225, 225, 225 + a, tags="dynamic") + canvas.create_line(125, 275, 225, 275 + a, tags="dynamic") + canvas.create_line(225, 225 - a, 425, 275 - a, tags="dynamic") + canvas.create_line(225, 275 - a, 425, 225 - a, tags="dynamic") + canvas.create_line(225, 225 + a, 425, 275 + a, tags="dynamic") + canvas.create_line(225, 275 + a, 425, 225 + a, tags="dynamic") + canvas.create_line(525, 225, 425, 225 - a, tags="dynamic") + canvas.create_line(525, 275, 425, 275 - a, tags="dynamic") + canvas.create_line(525, 225, 425, 225 + a, tags="dynamic") + canvas.create_line(525, 275, 425, 275 + a, tags="dynamic") + canvas.create_line(525, 225, 525, 275, dash=4, width=3*m-0.5, tags="dynamic") def update_dynamic_elements(device_canvas, sin_value, n_value): b = float(sin_value) @@ -95,3 +107,50 @@ def update_dynamic_elements(device_canvas, sin_value, n_value): # 根据a和m的值绘制动态元素 draw_dynamic_elements(device_canvas, a, m) + + +# 绘制网格,方便调试代码时定位坐标 +def draw_grid(canvas, line_distance): + # 获取画布的宽度和高度 + width = canvas.winfo_reqwidth() + height = canvas.winfo_reqheight() + # 绘制垂直线和垂直坐标 + for i in range(0, width, line_distance): + canvas.create_line(i, 0, i, height, fill="#d3d3d3") + canvas.create_text(i, 0, anchor='n', text=str(i)) + # 绘制水平线和水平坐标 + for i in range(0, height, line_distance): + canvas.create_line(0, i, width, i, fill="#d3d3d3") + canvas.create_text(0, i, anchor='w', text=str(i)) + + + +# 装置示意图函数调用示例 +if __name__ == '__main__': + root = tk.Tk() + root.title('装置示意图') + + # 先定下窗口的宽度和高度 + window_width = 600 + window_height = 600 + + # 获取用户屏幕尺寸,计算屏幕的中心位置 + screen_width = root.winfo_screenwidth() + screen_height = root.winfo_screenheight() + position_top = int((screen_height - window_height) / 2) + position_right = int((screen_width - window_width) / 2) + + # 设置窗口的位置 + root.geometry(f"{window_width}x{window_height}+{position_right}+{position_top}") + + # 设置窗口置顶 + root.attributes('-topmost', True) + + control_frame = tk.Frame(root, width=600, height=600) + control_frame.pack() + device_canvas = tk.Canvas(control_frame, width=600, height=600) + device_canvas.pack() + draw_grid(device_canvas, 50) + draw_static_elements(device_canvas) + draw_dynamic_elements(device_canvas, 10/(0.99**0.5), 1.0) + root.mainloop() \ No newline at end of file diff --git a/help.py b/help.py new file mode 100644 index 0000000..07544cc --- /dev/null +++ b/help.py @@ -0,0 +1,47 @@ +import tkinter as tk +from tkhtmlview import HTMLLabel +import markdown + +class HelpWindow: + def __init__(self, language='Chinese', font_size=14): + self.language = language + self.font_size = font_size + + self.window = tk.Tk() + self.window.title("帮助 / Help") + self.window.geometry("800x600") + + self.language_button = tk.Button(self.window, text="中文/English", command=self.toggle_language) + self.language_button.pack() + + self.html_content_chinese = self.convert_markdown_to_html("README.md") + self.html_content_english = self.convert_markdown_to_html("README.en.md") + + self.html_view = HTMLLabel(self.window, html=self.html_content_chinese) + self.html_view.pack(expand=True, fill="both") + + def toggle_language(self): + if self.language == 'Chinese': + self.language = 'English' + self.html_view.set_html(self.html_content_english) + else: + self.language = 'Chinese' + self.html_view.set_html(self.html_content_chinese) + + def convert_markdown_to_html(self, filename): + try: + with open(filename, "r", encoding="utf-8") as f: + markdown_content = f.read() + # 在 HTML 内容中使用 CSS 样式指定字体为微软雅黑 + html_content = f"" + html_content += markdown.markdown(markdown_content) + return html_content + except FileNotFoundError: + return "
x|km$UFo&+xCJ
zBVvEQpz#DOiyEW6+0znt&RgY5hrmP=(GjyXzjo4?{3*KQMNwz0J#0Kp+LW-NFYtsT
z?+e?Gs*y}{;x`9mP0GrKih1jG164n1ri#cjy?ZpsE{^Vs*6R)>NrL(3&4N`?E+qxT
zm%FS?AG%kAPjhe1dNEj?zg$FSNPIV8cMEQ_#~_{bo}pdZMA_D7J!7fk0_fpo(1^sk
z{)rHX!cy5Z&*8sun3J_brxV*1J!{+K=2`a`PVbf3?4zrd`标签,但保留其内容
+ html_content = html_content.replace('
', '').replace('
', '')
+ # 在 HTML 内容中为每个标签添加 CSS 样式
+ tags_to_replace = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'ol', 'ul', 'li', 'a']
+ # 针对每个需要替换的标签,使用字符串替换方法将其替换为带有楷体字体样式的标签
+ for tag in tags_to_replace:
+ html_content = html_content.replace(f"<{tag}>", f"<{tag} style=\"font-family: 微软雅黑, sans-serif;\">")
return html_content
except FileNotFoundError:
return "File not found
"
@@ -43,5 +50,6 @@ class HelpWindow:
self.window.mainloop()
if __name__ == "__main__":
- help_window = HelpWindow(font_size=14)
+ root = tk.Tk()
+ help_window = HelpWindow()
help_window.run()
diff --git a/main.py b/main.py
index d9631e6..a5951f0 100644
--- a/main.py
+++ b/main.py
@@ -8,7 +8,7 @@ from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from device import draw_static_elements, draw_dynamic_elements, update_dynamic_elements # 自定义模块,用于生成装置示意图
from mask import generate_mask # 自定义模块,用于生成掩膜版图
from result import generate_result # 自定义模块,用于生成计算结果图
-from help import show_help # 自定义模块,用于显示帮助文本
+from help import HelpWindow # 自定义类,用于渲染帮助文本
# 图像更新fig函数母版,用于更新掩膜版图和计算结果图区域的图像
@@ -115,6 +115,11 @@ def draw_grid(root, line_distance):
basic_canvas.create_line(0, i, width, i, fill="#d3d3d3")
basic_canvas.create_text(width / 2, i, anchor='w', text=str(i))
+# 设置帮助按钮的响应函数
+def show_help():
+ help_window = HelpWindow()
+ help_window.run()
+
# 设置关闭窗口时的处理函数
def on_closing():
@@ -143,7 +148,7 @@ root.geometry(f"{window_width}x{window_height}+{position_right}+{position_top}")
# 绘制基本坐标系,方便调试代码时定位坐标
-draw_grid(root, 50)
+#draw_grid(root, 50)
# 创建装置示意图容器
@@ -242,8 +247,8 @@ b_lable.place(x=50, y=821)
# 添加帮助按钮
-help_button = tk.Button(root, text="帮助", command=show_help, font=button_font, width=8)
-help_button.place(x=1800, y=10, anchor='ne')
+help_button = tk.Button(root, text="帮助/Help", command=show_help, font=button_font, width=10)
+help_button.place(x=1750, y=10, anchor='ne')
# 署名
ln_font = ('KaiTi', 20)
@@ -280,10 +285,6 @@ update_result()
root.protocol("WM_DELETE_WINDOW", on_closing)
-def print_size():
- print("Screen size: ", screen_width, screen_height)
- print("Window size: ", window_width, window_height)
-root.after(1000, print_size)
root.mainloop()
--
Gitee