python製作工程設計簡圖

前言

圖表跟圖片都是用來讓網頁元素更為豐富的手段,圖表為數據統計後的成果、圖片則為將抽象的文字轉換成圖片形式進行輸出(比如渠道的長相),抽象的文字往往不比圖片的介紹來的容易接受許多,而這也是資料可視化的目標之一。

使用情境

在提報工程概要表時,會需要讓上面了解未來要施作的工程內容,因此會拍攝現場的照片及附上工程設計簡圖,而工程設計簡圖的製作過程非常繁瑣,操作過程如下:

  1. 開啟CAD
  2. 畫出渠道形狀、標示文字內容
  3. 預覽列印
  4. 截圖預覽列印內容另存成圖片檔
  5. 圖片檔貼附到Excel上面

因此本次專案預期會讓使用者輸入渠道的尺寸、周邊環境關係來製作一張簡圖,放置於"工程概要表"讓長官了解未來要施作的工程內容,利用python的PIL套件縮短繪製工程設計簡圖的流程。

基本概念

PIL全名為Pillow,是目前在開發python時常用的一項繪圖工具

座標關係

PIL以第二象限開始,X往右遞增,Y往下遞增。 CAD以第一象限開始,X往右遞增,Y往遞增。

元素用法

畫線

1
2
def draw_line(draw, start_point, end_point, fill='black', width=3):
draw.line([start_point, end_point], fill=fill, width=width)

畫多段線

1
2
def draw_polyline(draw, points, fill='black', width=3):
draw.line(points, fill=fill, width=width)

寫文字

1
2
3
def draw_text(draw, text, position, font_size, font_path, fill='black'):
font = ImageFont.truetype(font_path, font_size)
draw.text(position, text, font=font, fill=fill)
  • 這裡的font_path如果是中文,可能會有出現亂碼的問題,可以先去Google font之類先下載下來到開發目錄讓他引用。目前中文的部分我用NotoSansTC-Regular.ttf

填充線

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def draw_polygon(draw, points, outline='black', fill=None, width=3):
draw.polygon(points, outline=outline, fill=fill)
if fill:
draw.line(points + [points[0]], fill=outline, width=width)

def draw_gradient_polygon(draw, vertices, start_color, end_color):
"""
繪製漸層顏色的多邊形

:param draw: PIL.ImageDraw對象
:param vertices: 多邊形頂點座標列表 [(x1, y1), (x2, y2), ...]
:param start_color: 開始顏色 (r, g, b)
:param end_color: 結束顏色 (r, g, b)
"""
x_coords, y_coords = zip(*vertices)
min_y, max_y = min(y_coords), max(y_coords)
height = max_y - min_y
gradient_colors = create_gradient_color(start_color, end_color, height)
for i, color in enumerate(gradient_colors):
draw.line([(min(x_coords), min_y + i), (max(x_coords), min_y + i)], fill=color)


標註尺寸

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def draw_dimension_line(draw, X1_DIM,Y1_DIM,X2_DIM,Y2_DIM, font_size, font_path,dim_space=20,arrow_length=10):

text_color='#e85fd6'
# font_size=30

if Y1_DIM==Y2_DIM:

dim_txt=str(int(X2_DIM-X1_DIM))

draw_text(draw, dim_txt,((X1_DIM+X2_DIM)/2-font_size/2,(Y1_DIM+Y2_DIM)/2-font_size*2.5-dim_space), font_size, font_path, fill=text_color)
draw_line(draw, (X1_DIM, Y1_DIM-dim_space), (X1_DIM, Y1_DIM-arrow_length-dim_space), fill=text_color, width=1)
draw_line(draw, (X2_DIM, Y2_DIM-dim_space), (X2_DIM, Y2_DIM-arrow_length-dim_space), fill=text_color, width=1)
draw_line(draw, (X1_DIM, Y1_DIM-arrow_length/2-dim_space), (X2_DIM, Y2_DIM-arrow_length/2-dim_space), fill=text_color, width=1)

elif X1_DIM==X2_DIM:

dim_txt=str(int(Y2_DIM-Y1_DIM))

draw_text(draw, dim_txt,((X1_DIM+X2_DIM)/2-dim_space-2.5*font_size,(Y1_DIM+Y2_DIM)/2-font_size), font_size, font_path, fill=text_color)
draw_line(draw, (X1_DIM-dim_space, Y1_DIM), (X1_DIM-arrow_length-dim_space, Y1_DIM), fill=text_color, width=1)
draw_line(draw, (X2_DIM-dim_space, Y2_DIM), (X2_DIM-arrow_length-dim_space, Y2_DIM), fill=text_color, width=1)
draw_line(draw, (X1_DIM-arrow_length/2-dim_space, Y1_DIM), (X2_DIM-arrow_length/2-dim_space, Y2_DIM), fill=text_color, width=1)

預期成果

Fig1.工程設計簡圖

完整做法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
from PIL import Image, ImageDraw, ImageFont

def draw_text(draw, text, position, font_size, font_path, fill='black'):
font = ImageFont.truetype(font_path, font_size)
draw.text(position, text, font=font, fill=fill)

def draw_line(draw, start_point, end_point, fill='black', width=3):
draw.line([start_point, end_point], fill=fill, width=width)

def draw_polygon(draw, points, outline='black', fill=None, width=3):
draw.polygon(points, outline=outline, fill=fill)
if fill:
draw.line(points + [points[0]], fill=outline, width=width)

def draw_polyline(draw, points, fill='black', width=3):
draw.line(points, fill=fill, width=width)

def create_gradient_color(start_color, end_color, height):
"""
創建垂直漸層顏色列表

:param start_color: 開始顏色 (r, g, b)
:param end_color: 結束顏色 (r, g, b)
:param height: 高度(漸層的總行數)
:return: 漸層顏色列表
"""
gradient = []
for i in range(height):
ratio = i / height
r = int(start_color[0] * (1 - ratio) + end_color[0] * ratio)
g = int(start_color[1] * (1 - ratio) + end_color[1] * ratio)
b = int(start_color[2] * (1 - ratio) + end_color[2] * ratio)
gradient.append((r, g, b))
return gradient

def draw_gradient_polygon(draw, vertices, start_color, end_color):
"""
繪製漸層顏色的多邊形

:param draw: PIL.ImageDraw對象
:param vertices: 多邊形頂點座標列表 [(x1, y1), (x2, y2), ...]
:param start_color: 開始顏色 (r, g, b)
:param end_color: 結束顏色 (r, g, b)
"""
x_coords, y_coords = zip(*vertices)
min_y, max_y = min(y_coords), max(y_coords)
height = max_y - min_y
gradient_colors = create_gradient_color(start_color, end_color, height)
for i, color in enumerate(gradient_colors):
draw.line([(min(x_coords), min_y + i), (max(x_coords), min_y + i)], fill=color)

def draw_dimension_line(draw, X1_DIM,Y1_DIM,X2_DIM,Y2_DIM, font_size, font_path,dim_space=20,arrow_length=10):

text_color='#e85fd6'
# font_size=30

if Y1_DIM==Y2_DIM:

dim_txt=str(int(X2_DIM-X1_DIM))

draw_text(draw, dim_txt,((X1_DIM+X2_DIM)/2-font_size/2,(Y1_DIM+Y2_DIM)/2-font_size*2.5-dim_space), font_size, font_path, fill=text_color)
draw_line(draw, (X1_DIM, Y1_DIM-dim_space), (X1_DIM, Y1_DIM-arrow_length-dim_space), fill=text_color, width=1)
draw_line(draw, (X2_DIM, Y2_DIM-dim_space), (X2_DIM, Y2_DIM-arrow_length-dim_space), fill=text_color, width=1)
draw_line(draw, (X1_DIM, Y1_DIM-arrow_length/2-dim_space), (X2_DIM, Y2_DIM-arrow_length/2-dim_space), fill=text_color, width=1)

elif X1_DIM==X2_DIM:

dim_txt=str(int(Y2_DIM-Y1_DIM))

draw_text(draw, dim_txt,((X1_DIM+X2_DIM)/2-dim_space-2.5*font_size,(Y1_DIM+Y2_DIM)/2-font_size), font_size, font_path, fill=text_color)
draw_line(draw, (X1_DIM-dim_space, Y1_DIM), (X1_DIM-arrow_length-dim_space, Y1_DIM), fill=text_color, width=1)
draw_line(draw, (X2_DIM-dim_space, Y2_DIM), (X2_DIM-arrow_length-dim_space, Y2_DIM), fill=text_color, width=1)
draw_line(draw, (X1_DIM-arrow_length/2-dim_space, Y1_DIM), (X2_DIM-arrow_length/2-dim_space, Y2_DIM), fill=text_color, width=1)

# 相關參數

def plot_Uchannel(B,H,T,env_left_txt,env_right_txt,env_left,env_right):

# B=200
# H=120
# T=25

X_left,X_right,Y_top,Y_bottom=150,150,150,100

X0=X_left+T+B/2
Y0=Y_top+H

# env_left_txt="路"
# env_left=20

# env_right_txt="田"
# env_right=60

env_left_body=H-env_left+T
env_right_body=H-env_right+T

# 設置圖像尺寸和解析度
width, height = X_left+X_right+2*T+B, Y_top+Y_bottom+H+T
dpi = 100 # 高 DPI 設置

# 創建圖像
image = Image.new('RGB', (width, height), 'white')
draw = ImageDraw.Draw(image)

# 字體設置
font_path = "NotoSansTC-Regular.ttf" # 字體文件的路徑
font_size = 20

# 繪製文字
draw_text(draw, env_left_txt, (X0-B/2-T-X_left/2-20, Y0-H+env_left-font_size*2), font_size*1.2, font_path)
draw_text(draw, env_right_txt, (X0+B/2+T+X_right/2, Y0-H+env_right-font_size*2), font_size*1.2, font_path)

# 土體
polygon_points = [ (X0-B/2-T, Y0-H+env_left), (X0-B/2-T-X_left, Y0-H+env_left), (X0-B/2-T-X_left, Y0-H+env_left+env_left_body), (X0-B/2-T, Y0-H+env_left+env_left_body)]
draw_gradient_polygon(draw, polygon_points, (51, 26, 0),(255, 255, 255))

# 土體

polygon_points = [ (X0+B/2+T, Y0-H+env_right), (X0+B/2+T+X_right, Y0-H+env_right), (X0+B/2+T+X_right, Y0-H+env_right+env_right_body), (X0+B/2+T, Y0-H+env_right+env_right_body)]
draw_gradient_polygon(draw, polygon_points, (51, 26, 0),(255, 255, 255))

# 面層

if env_left_txt=="田":
draw_line(draw, (X0-B/2-T, Y0-H+env_left), (X0-B/2-T-X_left, Y0-H+env_left), fill='green', width=4)
else:
draw_line(draw, (X0-B/2-T, Y0-H+env_left), (X0-B/2-T-X_left, Y0-H+env_left), fill=(0, 26, 51), width=4)

if env_right_txt=="路":
draw_line(draw, (X0+B/2+T, Y0-H+env_right), (X0+B/2+T+X_right, Y0-H+env_right), fill=(0, 26, 51), width=4)
else:
draw_line(draw, (X0+B/2+T, Y0-H+env_right), (X0+B/2+T+X_right, Y0-H+env_right), fill='green', width=4)

# 結構體

pts =[(X0,Y0),(X0-B/2,Y0),(X0-B/2,Y0-H),(X0-B/2-T,(Y0-H)),(X0-B/2-T,(Y0+T)),(X0+B/2+T,(Y0+T)),(X0+B/2+T,(Y0-H)),(X0+B/2,(Y0-H)),(X0+B/2,Y0),(X0,Y0)]

draw_polygon(draw, pts, outline='black', fill='gray', width=1)

## 尺寸標註

### 水平方向

X1_DIM,Y1_DIM=X0-B/2-T,Y0-H
X2_DIM,Y2_DIM=X0-B/2,Y0-H

draw_dimension_line(draw, X1_DIM,Y1_DIM,X2_DIM,Y2_DIM, font_size, font_path)

X1_DIM,Y1_DIM=X0-B/2,Y0-H
X2_DIM,Y2_DIM=X0+B/2,Y0-H

draw_dimension_line(draw, X1_DIM,Y1_DIM,X2_DIM,Y2_DIM, font_size, font_path)

X1_DIM,Y1_DIM=X0+B/2,Y0-H
X2_DIM,Y2_DIM=X0+B/2+T,Y0-H

draw_dimension_line(draw, X1_DIM,Y1_DIM,X2_DIM,Y2_DIM, font_size, font_path)

### 垂直方向

X1_DIM,Y1_DIM=X0-B/2-T,Y0-H
X2_DIM,Y2_DIM=X0-B/2-T,Y0

draw_dimension_line(draw, X1_DIM,Y1_DIM,X2_DIM,Y2_DIM, font_size, font_path)

X1_DIM,Y1_DIM=X0-B/2-T,Y0
X2_DIM,Y2_DIM=X0-B/2-T,Y0+T

draw_dimension_line(draw, X1_DIM,Y1_DIM,X2_DIM,Y2_DIM, font_size, font_path)

# 保存高解析度圖像
image.save("high_quality_drawing.png", dpi=(dpi, dpi))
image.show()

if __name__ == "__main__":
# 給定尺寸
plot_Uchannel(300,200,30,"田","田",20,50)