cannect 1.0.2__py3-none-any.whl → 1.0.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,394 @@
1
+ from pandas import DataFrame
2
+ import re
3
+
4
+
5
+ def to_dbc(file:str, df:DataFrame):
6
+
7
+ filtered_df = df
8
+ with open(file, 'w') as f:
9
+ # DBC 파일 기본 헤더 작성
10
+ f.write("VERSION \"\"\n\n\n"
11
+ "NS_ :\n"
12
+ " NS_DESC_\n"
13
+ " CM_\n"
14
+ " BA_DEF_\n"
15
+ " BA_\n"
16
+ " VAL_\n"
17
+ " CAT_DEF_\n"
18
+ " CAT_\n"
19
+ " FILTER\n"
20
+ " BA_DEF_DEF_\n"
21
+ " EV_DATA_\n"
22
+ " ENVVAR_DATA_\n"
23
+ " SGTYPE_\n"
24
+ " SGTYPE_VAL_\n"
25
+ " BA_DEF_SGTYPE_\n"
26
+ " BA_SGTYPE_\n"
27
+ " SIG_TYPE_REF_\n"
28
+ " VAL_TABLE_\n"
29
+ " SIG_GROUP_\n"
30
+ " SIG_VALTYPE_\n"
31
+ " SIGTYPE_VALTYPE_\n"
32
+ " BO_TX_BU_\n"
33
+ " BA_DEF_REL_\n"
34
+ " BA_REL_\n"
35
+ " BA_DEF_DEF_REL_\n"
36
+ " BU_SG_REL_\n"
37
+ " BU_EV_REL_\n"
38
+ " BU_BO_REL_\n"
39
+ " SG_MUL_VAL_\n\n"
40
+ "BS_:\n\n"
41
+ "BU_: \n\n")
42
+
43
+ processed_messages = set() # Message 기준으로 중복 체크 (Signal은 중복 허용)
44
+
45
+ for index, row in filtered_df.iterrows():
46
+ Message_id = row.get("ID", None)
47
+ Message_name = row.get("Message", "Unknown_Message")
48
+ DLC = row.get("DLC", None)
49
+
50
+ Send_ECU = row.get("ECU", None)
51
+ # ECU 컬럼이 공란일 경우 'Blank'로 설정
52
+ Send_ECU = Send_ECU if Send_ECU and str(Send_ECU).strip() else "Blank"
53
+
54
+
55
+ try:
56
+ Message_DLC = int(DLC) if DLC is not None else 8
57
+ except ValueError:
58
+ Message_DLC = 8
59
+
60
+ try:
61
+ Message_id = int(Message_id, 16) if Message_id is not None else None
62
+ except ValueError:
63
+ print(f"Warning: Message ID {Message_id} 변환 실패")
64
+ Message_id = None
65
+
66
+ if Message_name not in processed_messages: # Message 기준으로 중복 체크
67
+ f.write(f"BO_ {Message_id} {Message_name}: {Message_DLC} {Send_ECU}\n")
68
+ processed_messages.add(Message_name) # Message 기준으로 추가
69
+
70
+ Signal_Name = row.get("Signal", None)
71
+ if Signal_Name:
72
+ StartBit = row.get("Startbit", 0)#컬럼명 대소문자 구분 필@@@@@@@@@@@@@@@@@@@@@@@@@@@
73
+ Length = row.get("Length", 0)
74
+ ByteOrder = row.get("ByteOrder", "Intel")
75
+
76
+ Endian = 1 if ByteOrder == "Intel" else 0
77
+
78
+ Value_Type = row.get("Value Type", "Unsigned")
79
+ code = "+" if Value_Type == "Unsigned" else "-"
80
+
81
+ try:
82
+ StartBit = int(StartBit)
83
+ Length = int(Length)
84
+ Factor = float(row.get("Factor", 1)) # Factor는 float으로 처리
85
+ Offset = float(row.get("Offset", 0))
86
+
87
+ # Min과 Max 값이 비어 있을 경우 기본값 0 설정
88
+ Min = float(row.get("Min", 0) or 0)
89
+ Max = float(row.get("Max", 0) or 0)
90
+
91
+ Unit = row.get("Unit", None) # Unit 값을 가져옴
92
+
93
+ except ValueError:
94
+ Factor = 1.0
95
+ Offset = 0.0
96
+ Min = 0.0
97
+ Max = 0.0
98
+
99
+ # Factor 소수점 확인 후 출력 형식 조정
100
+ if isinstance(Factor, float) and Factor.is_integer():
101
+ Factor = int(Factor) # 소수점이 없으면 int로 변환
102
+ if isinstance(Offset, float) and Offset.is_integer():
103
+ Offset = int(Offset) # 소수점이 없으면 int로 변환
104
+ if isinstance(Min, float) and Min.is_integer():
105
+ Min = int(Min) # 소수점이 없으면 int로 변환
106
+ if isinstance(Max, float) and Max.is_integer():
107
+ Max = int(Max) # 소수점이 없으면 int로 변환
108
+
109
+ Sig_Receivers = row.get("Sig Receivers", "Vector__XXX")
110
+ # 값이 None이 아니고 문자열이 있는 경우 공백 제거
111
+ Sig_Receivers = "".join(Sig_Receivers.split()) if Sig_Receivers and str(
112
+ Sig_Receivers).strip() else "Blank"
113
+
114
+ f.write(f' SG_ {Signal_Name} : {StartBit}|{Length}@{Endian}{code} ({Factor},{Offset}) [{Min}|{Max}] "{Unit}" {Sig_Receivers}\n')
115
+
116
+ else:
117
+ print(f"Warning: Row {index}에 'Signal' 값이 없습니다.")
118
+ f.write("\n")
119
+
120
+ #------------------------------------------------------------------------------------
121
+
122
+ # 중복 방지용 집합
123
+ processed_messages = set() # Message 기준으로 중복 체크
124
+
125
+ for index, row in filtered_df.iterrows():
126
+ Message_name = row.get("Message", "Unknown_Message")
127
+ Message_id = row.get("ID", None)
128
+
129
+ Send_ECU = row.get("ECU", None)
130
+ # ECU 컬럼이 공란일 경우 'Blank'로 설정
131
+ Send_ECU = Send_ECU if Send_ECU and str(Send_ECU).strip() else "Blank"
132
+
133
+
134
+ try:
135
+ Message_id = int(Message_id, 16) if Message_id is not None else None
136
+ except ValueError:
137
+ print(f"Warning: Message ID {Message_id} 변환 실패")
138
+ Message_id = None
139
+
140
+ # 중복된 Message_name 방지
141
+ if Message_name not in processed_messages: # Message 기준으로 중복 체크
142
+ f.write(f"\nBO_TX_BU_ {Message_id} : {Send_ECU};")
143
+ processed_messages.add(Message_name) # Message 기준으로 추가
144
+
145
+ f.write("\n\n")
146
+ # ------------------------------------------------------------------------------------
147
+
148
+ processed_messages = set()
149
+
150
+ for index, row in filtered_df.iterrows():
151
+ Message_name = row.get("Message", "Unknown_Message")
152
+ Message_id = row.get("ID", None)
153
+ Send_Type = row.get("Send Type", None)
154
+
155
+ try:
156
+ Message_id = int(Message_id, 16) if Message_id is not None else None
157
+ except ValueError:
158
+ print(f"Warning: Message ID {Message_id} 변환 실패")
159
+ Message_id = None
160
+
161
+ if Message_name not in processed_messages: # Message 기준으로 중복 체크
162
+ # Send_Type에 따른 주석 값 설정
163
+ if Send_Type == "EW":
164
+ send_type_value = "[EW] On Event and On Write"
165
+ elif Send_Type == "P":
166
+ send_type_value = "[P] Periodic"
167
+ elif Send_Type == "EC":
168
+ send_type_value = "[EC] On Event and On Change"
169
+ elif Send_Type == "PE":
170
+ send_type_value = "[PE] Periodic and On Event"
171
+ else:
172
+ send_type_value = ""
173
+
174
+
175
+ f.write(f"CM_ BO_ {Message_id} \"{send_type_value}\";\n")
176
+ processed_messages.add(Message_name) # Message 기준으로 추가
177
+
178
+ Signal_Name = row.get("Signal", None)
179
+ if Signal_Name:
180
+ Definition = row.get("Definition", None) # Definition 값을 가져옴
181
+
182
+ if Definition: # Definition 값이 존재할 경우에만 처리
183
+ f.write(f"CM_ SG_ {Message_id} {Signal_Name} \"{Definition}\";\n")
184
+
185
+ f.write("\n")
186
+ # ------------------------------------------------------------------------------------
187
+
188
+ f.write(
189
+ 'BA_DEF_ BO_ "VFrameFormat" ENUM "StandardCAN","ExtendedCAN","reserved","reserved","reserved","reserved","reserved","reserved","reserved","reserved","reserved","reserved","reserved","reserved","StandardCAN_FD","ExtendedCAN_FD";\n'
190
+ 'BA_DEF_ BO_ "GenMsgSendType" ENUM "Cyclic","NoMsgSendType";\n'
191
+ 'BA_DEF_ BO_ "GenMsgCycleTime" INT 0 2000;\n'
192
+ )
193
+ f.write("\n")
194
+ # ------------------------------------------------------------------------------------
195
+
196
+ f.write(
197
+ 'BA_DEF_ SG_ "GenSigStartValue" INT 0 1215752192;\n'
198
+ 'BA_DEF_ SG_ "UserSigValidity" ENUM "B+", "ACC", "IG", "IG1", "IG2", "IG3", "IG1 or IG2", "IG1 or IG3", "Undef";\n'
199
+ 'BA_DEF_ SG_ "GenSigSendType" ENUM "Cyclic", "OnWrite", "OnWriteWithRepetition", "OnChange", "OnChangeWithRepetition", "IfActive", "IfActiveWithRepetition", "NoSigSendType", "OnChangeAndIfActive", "OnChangeAndIfActiveWithRepetition";\n'
200
+ )
201
+ f.write("\n")
202
+ # ------------------------------------------------------------------------------------
203
+ #DBC 내 Networks 컬럼 생성
204
+ f.write(
205
+ 'BA_DEF_ "DBName" STRING ;\n'
206
+ 'BA_DEF_ "BusType" STRING ;\n'
207
+ 'BA_DEF_ "Baudrate" INT 50000 1000000;\n'
208
+ 'BA_DEF_ "Manufacturer" STRING ;\n'
209
+ )
210
+ f.write("\n")
211
+ # ------------------------------------------------------------------------------------
212
+ # DBC 내 Networks에 생성된 컬럼의 값 삽입
213
+ # BA_DEF_DEF_ 블록 작성
214
+ f.write(
215
+ 'BA_DEF_DEF_ "Manufacturer" "(c)HYUNDAI KEFICO CAN그룹";\n'
216
+ 'BA_DEF_DEF_ "DBName" "CANFD_V01";\n'
217
+ 'BA_DEF_DEF_ "BusType" "CAN FD";\n'
218
+ 'BA_DEF_DEF_ "Baudrate" 500000;\n'
219
+ 'BA_DEF_DEF_ "GenSigStartValue" 0;\n'
220
+ 'BA_DEF_DEF_ "GenMsgSendType" "NoMsgSendType";\n'
221
+ 'BA_DEF_DEF_ "UserSigValidity" "IG";\n'
222
+ 'BA_DEF_DEF_ "GenMsgCycleTime" 200;\n'
223
+ )
224
+ f.write("\n")
225
+ # ------------------------------------------------------------------------------------
226
+ # BA_ 블록 작성
227
+
228
+ processed_messages = set()
229
+
230
+ for index, row in filtered_df.iterrows():
231
+ Message_name = row.get("Message", "Unknown_Message")
232
+ Message_id = row.get("ID", None)
233
+ cycle_time = row.get("Cycle Time", None)
234
+ Send_Type = row.get("Send Type", None)
235
+
236
+ try:
237
+ # Convert cycle_time to int, if available
238
+ cycle_time = int(cycle_time) if cycle_time is not None else None
239
+ except ValueError:
240
+ print(f"Warning: Cycle Time {cycle_time} 변환 실패")
241
+ cycle_time = None
242
+
243
+ try:
244
+ Message_id = int(Message_id, 16) if Message_id is not None else None
245
+ except ValueError:
246
+ print(f"Warning: Message ID {Message_id} 변환 실패")
247
+ Message_id = None
248
+
249
+ if Message_name not in processed_messages:
250
+ # Send_Type 값에 따른 처리
251
+ send_type_value = 0 if Send_Type in ["P", "PE"] else 1
252
+
253
+ f.write(
254
+ f'BA_ "GenMsgSendType" BO_ {Message_id} {send_type_value};\n'
255
+ f'BA_ "GenMsgCycleTime" BO_ {Message_id} {cycle_time};\n'
256
+ #f'BA_ "GenMsgILSupport" BO_ {Message_id} 0;\n'
257
+ #f'BA_ "TpMessage" BO_ {Message_id} 1;\n'
258
+ #f'BA_ "GenMsgDelayTime" BO_ {Message_id} 0;\n'
259
+ #f'BA_ "GenMsgStartDelayTime" BO_ {Message_id} 0;\n'
260
+ f'BA_ "VFrameFormat" BO_ {Message_id} 14;\n\n'
261
+ )
262
+ processed_messages.add(Message_name)
263
+
264
+ f.write("\n")
265
+
266
+ # ------------------------------------------------------------------------------------
267
+ # BA_ 블록 작성
268
+
269
+ for index, row in filtered_df.iterrows():
270
+ Message_name = row.get("Message", "Unknown_Message")
271
+ Message_id = row.get("ID", None)
272
+ Signal_Name = row.get("Signal", None)
273
+ GenSigStartValue = row.get("GenSigStartValue", None)
274
+ UserSigValidity = row.get("UserSigValidity", None)
275
+
276
+ if Message_id is not None:
277
+ try:
278
+ # 16진수로 변환
279
+ Message_id = int(Message_id, 16)
280
+ GenSigStartValue = int(GenSigStartValue, 16)
281
+ except ValueError:
282
+ print(f"Warning: Message ID {Message_id} 변환 실패")
283
+ Message_id = None
284
+ GenSigStartValue = None
285
+
286
+ # UserSigValidity 값을 변환
287
+ user_sig_validity_map = {
288
+ "B+": 0,
289
+ "ACC": 1,
290
+ "IG": 2,
291
+ "IG1": 3,
292
+ "IG2": 4,
293
+ "IG3": 5,
294
+ "IG1 or IG2": 6,
295
+ "IG1 or IG3": 7,
296
+ "Undef": 8
297
+ }
298
+ UserSigValidity = user_sig_validity_map.get(UserSigValidity, 8) # 기본값은 8로 설정
299
+
300
+ # 파일에 작성
301
+ if Message_id is not None:
302
+ send_type_value = 0 if Send_Type in ["P", "PE"] else 1
303
+
304
+ f.write(
305
+ #f'BA_ "GenSigSendType" SG_ {Message_id} {Signal_Name} {};\n'
306
+ f'BA_ "GenSigStartValue" SG_ {Message_id} {Signal_Name} {GenSigStartValue};\n'
307
+ f'BA_ "UserSigValidity" SG_ {Message_id} {Signal_Name} {UserSigValidity};\n'
308
+ )
309
+
310
+ f.write("\n")
311
+ # ------------------------------------------------------------------------------------
312
+
313
+ for index, row in filtered_df.iterrows():
314
+ Message_id = row.get("ID", None)
315
+ Signal_Name = row.get("Signal", None)
316
+ value_Table = row.get("Value Table", None)
317
+
318
+ # value_Table이 없으면 해당 행을 건너뛰기
319
+ if value_Table is None or value_Table == "":
320
+ continue
321
+
322
+ # value_Table에서 따옴표(") 제거
323
+ value_Table = value_Table.replace('"', '')
324
+
325
+ # Message ID 16진수 변환
326
+ if Message_id is not None:
327
+ try:
328
+ Message_id = int(Message_id, 16) # 16진수에서 10진수로 변환
329
+ except ValueError:
330
+ print(f"Warning: Message ID {Message_id} 변환 실패")
331
+ Message_id = None
332
+
333
+ # value_Table 값 변환
334
+ if value_Table is not None:
335
+ # 각 항목을 /로 구분
336
+ items = value_Table.split('/')
337
+
338
+ converted_value_table = []
339
+ for item in items:
340
+ # B0:Description 형식 처리 (비트 자리 값)
341
+ match = re.match(r"B(\d+):(.+)", item.strip())
342
+ if match:
343
+ bit_position = int(match.group(1)) # 비트 자리수
344
+ description = match.group(2).strip()
345
+
346
+ # 2의 비트 자리수 승 계산
347
+ bit_value = 2 ** bit_position
348
+
349
+ # 변환된 값과 설명을 리스트에 추가
350
+ converted_value_table.append(f'{bit_value} "{description}"')
351
+
352
+ else:
353
+ # 기존 처리: 0x0~0xFFFF:CRCValue 또는 0x0:Description 형식 처리
354
+ match = re.match(r"(0x[0-9A-F]+)~(0x[0-9A-F]+):(.+)", item.strip())
355
+ if match:
356
+ start_hex = match.group(1)
357
+ end_hex = match.group(2)
358
+ description = match.group(3).strip()
359
+
360
+ # 16진수 값을 10진수로 변환
361
+ start_dec = int(start_hex, 16)
362
+ end_dec = int(end_hex, 16)
363
+
364
+ # 변환된 값과 설명을 리스트에 추가
365
+ converted_value_table.append(
366
+ f'{start_dec} "{start_hex}~{end_hex}:{description}" {end_dec} "{start_hex}~{end_hex}:{description}"')
367
+ else:
368
+ # 0x0:Description 형식 처리 (범위가 없을 경우)
369
+ match = re.match(r"(0x[0-9A-F]+):(.+)", item.strip())
370
+ if match:
371
+ hex_value = match.group(1)
372
+ description = match.group(2).strip()
373
+
374
+ # 16진수 값을 10진수로 변환
375
+ try:
376
+ decimal_value = int(hex_value, 16)
377
+ # 변환된 값과 설명을 리스트에 추가
378
+ converted_value_table.append(f'{decimal_value} "{description}"')
379
+ except ValueError:
380
+ print(f"Warning: {hex_value} 변환 실패")
381
+ continue # 변환 실패 시 해당 항목은 건너뜀
382
+
383
+ # 변환된 값들이 하나도 없으면 해당 행은 표출하지 않음
384
+ if converted_value_table:
385
+ value_table_str = " ".join(converted_value_table)
386
+ else:
387
+ value_table_str = None # 변환된 값이 없으면 value_table_str을 None으로 설정
388
+
389
+ # 파일에 작성 (value_table_str이 None이 아니면 작성)
390
+ if Message_id is not None and Signal_Name and value_table_str:
391
+ f.write(f'VAL_ {Message_id} {Signal_Name} {value_table_str} ;\n')
392
+
393
+
394
+ f.write("\n")
@@ -1,11 +1,13 @@
1
+ from cannect.config import env
1
2
  from cannect.core.can.db.schema import SCHEMA
2
3
  from cannect.core.can.db.vcs import CANDBVcs
4
+ from cannect.core.can.db._dbc import to_dbc
5
+ from cannect.errors import CANDBDuplicationError
3
6
  from cannect.schema.candb import CanSignal, CanMessage
4
7
  from cannect.schema.datadictionary import DataDictionary
5
8
 
6
-
7
9
  from pandas import DataFrame
8
- from typing import Dict, Union
10
+ from typing import Union
9
11
  import pandas as pd
10
12
  import os
11
13
 
@@ -17,14 +19,14 @@ class CANDBReader:
17
19
  """
18
20
  def __init__(self, src:Union[str, DataFrame]='', **kwargs):
19
21
  if isinstance(src, DataFrame):
20
- source = kwargs["source"] if "source" in kwargs else 'direct'
21
- traceability = kwargs["traceability"] if "traceability" in kwargs else 'Untraceable'
22
+ source = kwargs.get('source', 'direct')
23
+ traceability = kwargs.get("traceability",'untraceable')
22
24
  __db__ = src.copy()
23
25
  else:
24
26
  if not str(src):
25
27
  src = CANDBVcs().json
26
28
  source = src
27
- traceability = "_".join(os.path.basename(source).split("_")[:-1])
29
+ traceability = "_".join(os.path.basename(source).split(".")[:-1])
28
30
  __db__ = pd.read_json(source, orient='index')
29
31
 
30
32
  __db__ = __db__[~__db__["Message"].isna()]
@@ -70,12 +72,17 @@ class CANDBReader:
70
72
  except AttributeError:
71
73
  return self.db.__getattr__(item)
72
74
 
75
+ def _check_engine_spec(self, engine_spec:str):
76
+ if not engine_spec.lower() in ["ice", "hev"]:
77
+ raise KeyError(f'Unsupported engine_spec: {engine_spec.upper()}')
78
+ return engine_spec.upper()
79
+
73
80
  @property
74
81
  def messages(self) -> DataDictionary:
75
82
  return DataDictionary({msg:CanMessage(df) for msg, df in self.db.groupby(by="Message")})
76
83
 
77
84
  @property
78
- def signals(self) -> Union[Dict[str, CanSignal], DataDictionary]:
85
+ def signals(self) -> DataDictionary:
79
86
  return DataDictionary({str(sig["Signal"]):CanSignal(sig) for _, sig in self.db.iterrows()})
80
87
 
81
88
  def mode(self, engine_spec:str):
@@ -84,6 +91,41 @@ class CANDBReader:
84
91
  def is_developer_mode(self):
85
92
  return "Channel" in self.db.columns
86
93
 
94
+ def to_dbc(self, engine_spec:str, channel:Union[int, str], **kwargs):
95
+ engine_spec = self._check_engine_spec(engine_spec)
96
+ if isinstance(channel, int):
97
+ if engine_spec == "HEV":
98
+ channel = {1:'P', 2:'H', 3:'L'}[channel]
99
+ else:
100
+ channel = {1:'P', 2:'L', 3:'L'}[channel]
101
+ if not channel.upper() in ['P', 'H', 'L']:
102
+ raise KeyError(f'Unsupported channel: {channel.upper()}')
103
+ channel = channel.upper()
104
+
105
+ base = self.mode(engine_spec).db
106
+ base = base[base[f'{engine_spec} Channel'] == channel]
107
+ if 'Codeword' in kwargs:
108
+ base = base[base['Codeword'].str.replace(" ", "") == kwargs['Codeword'].replace(" ", "")]
109
+ if 'SystemConstant' in kwargs:
110
+ base = base[base['SystemConstant'].str.replace(" ", "") == kwargs['SystemConstant'].replace(" ", "")]
111
+
112
+ for _id, df in base.groupby("ID"):
113
+ msg = df["Message"].unique()
114
+ if len(msg) > 1:
115
+ raise CANDBDuplicationError(f'IN CHANNEL: {channel}, ID: "{_id}" IS DUPLICATED BY "{msg.tolist()}", SPECIFY {{Codeword}} or {{SystemConstant}}')
116
+
117
+ if channel == 'L' and engine_spec == 'HEV':
118
+ n_channel = '3'
119
+ else:
120
+ n_channel = {'P': 1, 'H': 2, 'L': '2, 3'}[channel]
121
+ filename = f'{engine_spec}-CAN{n_channel}'
122
+ if kwargs:
123
+ filename += "-" + "-".join([f'{{{val}}}' for val in kwargs.values()])
124
+ filename += '.dbc'
125
+ to_dbc(env.DOWNLOADS / filename, base)
126
+ return
127
+
128
+
87
129
  def to_developer_mode(self, engine_spec:str):
88
130
  """
89
131
  :param engine_spec: ["ICE", "HEV"]
@@ -137,12 +179,16 @@ if __name__ == "__main__":
137
179
 
138
180
  db = CANDBReader()
139
181
  # print(db)
140
- print(db.source)
141
- print(db.traceability)
142
- print(db.revision)
182
+ # print(db.source)
183
+ # print(db.traceability)
184
+ # print(db.revision)
143
185
  # print(db.to_developer_mode("ICE").revision)
144
186
  # print(db.messages['ABS_ESC_01_10ms'])
145
187
  # print(db.columns)
146
188
  # print(db.is_developer_mode())
147
189
  # db2 = db.to_developer_mode("HEV")
148
- # print(db2[db2["Message"].str.contains("MCU")])
190
+ # print(db2[db2["Message"].str.contains("MCU")])
191
+
192
+ db.to_dbc("ICE", 1, Codeword="Cfg_CanSTDDB_C==2")
193
+ db.to_dbc("ICE", 2)
194
+ db.to_dbc("ICE", 3)
@@ -7,9 +7,8 @@ from cannect.utils import tools
7
7
 
8
8
  from datetime import datetime
9
9
  from docx import Document
10
- from docx.parts.styles import Styles
11
10
  from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
12
- from docx.shared import RGBColor, Pt, Inches
11
+ from docx.shared import Inches
13
12
  from pandas import DataFrame
14
13
  from pathlib import Path
15
14
  from tqdm.auto import tqdm
@@ -22,7 +21,7 @@ class Specification:
22
21
  def __init__(self, db:Union[CANDBReader, DataFrame]):
23
22
  if isinstance(db, DataFrame):
24
23
  db = CANDBReader(db)
25
- self.db = db
24
+ self._db = db
26
25
 
27
26
  if not (env.SVN_CANDB / r"사양서/resource").exists():
28
27
  Subversion.update(env.SVN_CANDB / "사양서")
@@ -35,101 +34,101 @@ class Specification:
35
34
  os.rename(template.parent / 'default', template.parent / 'default.docx')
36
35
  time.sleep(0.5)
37
36
 
38
- self.doc = doc = Document()
39
- self.styles = CustomStyle(doc)
37
+ self._doc = doc = Document()
38
+ self._style = CustomStyle(doc)
40
39
  return
41
40
 
42
- def set_title(self):
43
- self.doc.add_paragraph(
41
+ def _set_title(self):
42
+ self._doc.add_paragraph(
44
43
  "\n자체제어기 EMS/ASW\n통신 사양서(CAN-FD)\n\n",
45
- style=self.styles.title
44
+ style=self._style.title
46
45
  )
47
46
  return
48
47
 
49
- def set_ci(self):
48
+ def _set_ci(self):
50
49
  png = env.SVN_CANDB / r"사양서/resource/ci_cover.png"
51
- paragraph = self.doc.add_paragraph()
50
+ paragraph = self._doc.add_paragraph()
52
51
  runner = paragraph.add_run()
53
52
  runner.add_picture(str(png), width=Inches(6))
54
53
  paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
55
- self.doc.add_paragraph("\n", style=self.styles.title)
54
+ self._doc.add_paragraph("\n", style=self._style.title)
56
55
  return
57
56
 
58
- def set_overview(self):
57
+ def _set_overview(self):
59
58
  items = {
60
- 'DATABASE': f"자체제어기/EMS CAN-FD {self.db.revision}",
59
+ 'DATABASE': f"자체제어기/EMS CAN-FD {self._db.revision}",
61
60
  'COMPANY': env.COMPANY,
62
61
  'DIVISION': env.DIVISION,
63
62
  'RELEASE': datetime.now().strftime("%Y-%m-%d"),
64
63
  }
65
64
 
66
- table = self.doc.add_table(rows=len(items), cols=2)
65
+ table = self._doc.add_table(rows=len(items), cols=2)
67
66
  table.style = 'Table Grid'
68
67
  for n, (key, value) in enumerate(items.items()):
69
68
  left = table.rows[n].cells[0]
70
69
  left.width = Inches(1)
71
70
  name = left.paragraphs[0]
72
- name.style = self.styles.overview_left
71
+ name.style = self._style.overview_left
73
72
  name.text = key
74
73
 
75
74
  right = table.rows[n].cells[1]
76
- right.width = self.doc.sections[0].page_width
75
+ right.width = self._doc.sections[0].page_width
77
76
  text = right.paragraphs[0]
78
- text.style = self.styles.overview_right
77
+ text.style = self._style.overview_right
79
78
  text.text = value
80
79
  return
81
80
 
82
- def set_margin(self):
83
- section = self.doc.sections[0]
81
+ def _set_margin(self):
82
+ section = self._doc.sections[0]
84
83
  section.left_margin = \
85
84
  section.right_margin = Inches(0.5)
86
85
  section.bottom_margin = \
87
86
  section.top_margin = Inches(1)
88
87
  return
89
88
 
90
- def set_header(self, version:str):
91
- header = self.doc.sections[0].header
89
+ def _set_header(self, version:str):
90
+ header = self._doc.sections[0].header
92
91
  for paragraph in header.paragraphs:
93
92
  p = getattr(paragraph, "_element")
94
93
  p.getparent().remove(p)
95
94
 
96
- table = header.add_table(rows=1, cols=3, width=self.doc.sections[0].page_width)
95
+ table = header.add_table(rows=1, cols=3, width=self._doc.sections[0].page_width)
97
96
 
98
97
  left = table.rows[0].cells[0].paragraphs[0]
99
98
  left.text = "자체제어기 EMS/ASW CAN-FD" + "\n" + f"DOC {version}"
100
- left.style = self.styles.header_left
99
+ left.style = self._style.header_left
101
100
 
102
101
  right = table.rows[0].cells[2].paragraphs[0]
103
102
  right.text = env.DIVISION + "\n" + env.COMPANY
104
- right.style = self.styles.header_right
103
+ right.style = self._style.header_right
105
104
  return
106
105
 
107
- def set_footer(self):
108
- footer = self.doc.sections[0].footer
106
+ def _set_footer(self):
107
+ footer = self._doc.sections[0].footer
109
108
  for paragraph in footer.paragraphs:
110
109
  p = getattr(paragraph, "_element")
111
110
  p.getparent().remove(p)
112
- table = footer.add_table(rows=1, cols=1, width=self.doc.sections[0].page_width)
111
+ table = footer.add_table(rows=1, cols=1, width=self._doc.sections[0].page_width)
113
112
  cell = table.rows[0].cells[0].paragraphs[0]
114
113
  cell.text = env.COPYRIGHT
115
- cell.style = self.styles.footer
114
+ cell.style = self._style.footer
116
115
  return
117
116
 
118
117
  def generate(self, filename:str):
119
118
  if not filename.endswith('.docx'):
120
119
  filename += '.docx'
121
- objs = [(msg, obj) for msg, obj in self.db.messages.items()]
120
+ objs = [(msg, obj) for msg, obj in self._db.messages.items()]
122
121
  objs = sorted(objs, key=lambda x: x[0])
123
122
 
124
- self.set_margin()
125
- self.set_header(self.db.revision)
126
- self.set_footer()
127
- self.set_title()
128
- self.set_ci()
129
- self.set_overview()
123
+ self._set_margin()
124
+ self._set_header(self._db.revision)
125
+ self._set_footer()
126
+ self._set_title()
127
+ self._set_ci()
128
+ self._set_overview()
130
129
 
131
- self.doc.add_section()
132
- message = Message(self.doc)
130
+ self._doc.add_section()
131
+ message = Message(self._doc)
133
132
  message.addHeading("EMS TRANSMIT")
134
133
  transmit = tqdm([obj for _, obj in objs if obj["ECU"] == "EMS"])
135
134
  for obj in transmit:
@@ -150,7 +149,7 @@ class Specification:
150
149
  message.addSignalList(obj)
151
150
  message.addSignalProperty(obj)
152
151
 
153
- self.doc.save(env.DOWNLOADS / filename)
152
+ self._doc.save(env.DOWNLOADS / filename)
154
153
  return
155
154
 
156
155