hytos / DTI_PID / DTI_PID / ImportTextFromPDFDialog.py @ dd913487
이력 | 보기 | 이력해설 | 다운로드 (14.8 KB)
1 |
# coding: utf-8
|
---|---|
2 |
"""This is text importing module from AutoCAD"""
|
3 |
import os |
4 |
import sys |
5 |
from PyQt5.QtCore import * |
6 |
from PyQt5.QtGui import * |
7 |
from PyQt5.QtWidgets import * |
8 |
from AppDocData import AppDocData |
9 |
import subprocess |
10 |
from xml.etree.ElementTree import Element, SubElement, dump, ElementTree, parse |
11 |
|
12 |
import ImportTextFromPDF_UI |
13 |
|
14 |
|
15 |
class QImportTextFromPDFDialog(QDialog): |
16 |
def __init__(self, parent): |
17 |
QDialog.__init__(self, parent)
|
18 |
|
19 |
self.ui = ImportTextFromPDF_UI.Ui_ImportTextFromPDFDialog()
|
20 |
self.ui.setupUi(self) |
21 |
|
22 |
self._dwgs = []
|
23 |
|
24 |
app_doc_data = AppDocData.instance() |
25 |
|
26 |
configs = app_doc_data.getConfigs('PDF Size', 'Width') |
27 |
self.ui.spinBoxWidth.setValue(int(configs[0].value)) if 1 == len(configs) else \ |
28 |
self.ui.spinBoxWidth.setValue(0) |
29 |
configs = app_doc_data.getConfigs('PDF Size', 'Height') |
30 |
self.ui.spinBoxHeight.setValue(int(configs[0].value)) if 1 == len(configs) else \ |
31 |
self.ui.spinBoxHeight.setValue(0) |
32 |
configs = app_doc_data.getConfigs('PDF Offset', 'X') |
33 |
self.ui.spinBoxTextX.setValue(int(configs[0].value)) if 1 == len(configs) else \ |
34 |
self.ui.spinBoxTextX.setValue(0) |
35 |
configs = app_doc_data.getConfigs('PDF Offset', 'Y') |
36 |
self.ui.spinBoxTextY.setValue(int(configs[0].value)) if 1 == len(configs) else \ |
37 |
self.ui.spinBoxTextY.setValue(0) |
38 |
configs = app_doc_data.getConfigs('PDF Offset', 'Scale') |
39 |
self.ui.doubleSpinBoxScale.setValue(float(configs[0].value)) if 1 == len(configs) else \ |
40 |
self.ui.doubleSpinBoxScale.setValue(0.5) |
41 |
self.text_scale = None |
42 |
|
43 |
self.isAccepted = False |
44 |
|
45 |
self.ui.toolButtonPDF.clicked.connect(self.on_add_PDF_click) |
46 |
self.ui.pushButtonSave.clicked.connect(self.on_save_setting) |
47 |
self.ui.pushButtonImport.clicked.connect(self.on_import_pdf) |
48 |
self.ui.pushButtonClose.clicked.connect(self.close) |
49 |
|
50 |
|
51 |
def on_add_PDF_click(self): |
52 |
"""select drawing files"""
|
53 |
project = AppDocData.instance().getCurrentProject() |
54 |
|
55 |
options = QFileDialog.Options() |
56 |
options |= QFileDialog.DontUseNativeDialog |
57 |
files, _ = QFileDialog.getOpenFileNames(self, self.tr('Import', "Select Drawing File"), |
58 |
project.getDrawingFilePath(), "png files (*.png)", options=options)
|
59 |
if files:
|
60 |
self.ui.lineEditDrawing.setText(files[0]) if len(files) == 1 else \ |
61 |
self.ui.lineEditDrawing.setText(str(len(files)) + ' files') |
62 |
|
63 |
self._dwgs.clear()
|
64 |
for file in files: |
65 |
if os.path.exists(file): |
66 |
self._dwgs.append(file) |
67 |
|
68 |
def on_save_setting(self): |
69 |
"""save setting"""
|
70 |
from AppDocData import Config |
71 |
|
72 |
configs = [] |
73 |
configs.append(Config('PDF Size', 'Width', self.ui.spinBoxWidth.value())) |
74 |
configs.append(Config('PDF Size', 'Height', self.ui.spinBoxHeight.value())) |
75 |
configs.append(Config('PDF Offset', 'X', self.ui.spinBoxTextX.value())) |
76 |
configs.append(Config('PDF Offset', 'Y', self.ui.spinBoxTextY.value())) |
77 |
configs.append(Config('PDF Offset', 'Scale', self.ui.doubleSpinBoxScale.value())) |
78 |
|
79 |
app_doc_data = AppDocData.instance() |
80 |
app_doc_data.saveConfigs(configs) |
81 |
|
82 |
QMessageBox.information(self, self.tr('Information'), self.tr('Successfully saved.')) |
83 |
|
84 |
def on_import_pdf(self): |
85 |
"""import line and text from autocad"""
|
86 |
import XmlGenerator as xg |
87 |
from Area import Area |
88 |
|
89 |
project = AppDocData.instance().getCurrentProject() |
90 |
|
91 |
temp_path = project.getTempPath() |
92 |
id2_xml_files = [f for f in os.listdir(temp_path) if os.path.isfile(os.path.join(temp_path, f)) and |
93 |
(os.path.splitext(f)[1].upper() == '.XML')] |
94 |
|
95 |
self.text_scale = [self.ui.spinBoxWidth.value(), self.ui.spinBoxHeight.value(), self.ui.spinBoxTextX.value(), self.ui.spinBoxTextY.value(), self.ui.doubleSpinBoxScale.value()] |
96 |
|
97 |
for _file in self._dwgs: |
98 |
file_name = os.path.splitext(os.path.basename(_file))[0]
|
99 |
matches = [id2_xml_file for id2_xml_file in id2_xml_files if id2_xml_file.replace(file_name, '').upper() == '.XML'] |
100 |
if matches:
|
101 |
try:
|
102 |
symbol_areas = [] |
103 |
pdf_xml_path = os.path.join(os.path.dirname(_file), os.path.splitext(os.path.basename(_file))[0] + '.xml') |
104 |
pdf_xml = parse(pdf_xml_path) |
105 |
|
106 |
id2_xml_path = os.path.join(temp_path, matches[0])
|
107 |
id2_xml = parse(id2_xml_path) |
108 |
|
109 |
pdf_xml_root = pdf_xml.getroot() |
110 |
id2_xml_root = id2_xml.getroot() |
111 |
|
112 |
for symbol in id2_xml_root.find('SYMBOLS').iter('SYMBOL'): |
113 |
pt = [float(x) for x in symbol.find('LOCATION').text.split(',')] |
114 |
size = [float(x) for x in symbol.find('SIZE').text.split(',')] |
115 |
area = Area('Size')
|
116 |
area.x, area.y = pt[0], pt[1] |
117 |
area.width, area.height = size[0], size[1] |
118 |
symbol_areas.append(area) |
119 |
|
120 |
"""remove texts from id2 xml file"""
|
121 |
textInfo = id2_xml_root.find(xg.TEXT_INFO_LIST_NODE_NAME) |
122 |
textInfo.clear() |
123 |
|
124 |
noteInfo = id2_xml_root.find(xg.NOTE_TEXT_INFO_LIST_NOTE_NAME) |
125 |
noteInfo.clear() |
126 |
|
127 |
lineNoInfo = id2_xml_root.find(xg.LINE_NOS_NODE_NAME) |
128 |
lineNoInfo.clear() |
129 |
"""up to here"""
|
130 |
|
131 |
"""remove unknowns from id2 xml file"""
|
132 |
unknowns = id2_xml_root.find(xg.UNKNOWNS_NODE_NAME) |
133 |
unknowns.clear() |
134 |
"""up to here"""
|
135 |
|
136 |
"""add text from pdf file to id xml file"""
|
137 |
rects = [] |
138 |
|
139 |
for blk_tbl_record in pdf_xml_root.iter('Text'): |
140 |
position = [float(token) for token in blk_tbl_record.attrib['Position'].split(',')] |
141 |
|
142 |
rect = self.make_text_box(blk_tbl_record, position)
|
143 |
rects.append(rect) |
144 |
|
145 |
nodes = self.texts_to_xml(rects, symbol_areas)
|
146 |
if nodes:
|
147 |
textInfo.extend(nodes) |
148 |
|
149 |
id2_xml.write(id2_xml_path, encoding="utf-8", xml_declaration=True) |
150 |
"""up to here"""
|
151 |
|
152 |
QMessageBox.information(self, self.tr('Information'), self.tr('Importing Success!'), |
153 |
QMessageBox.Close) |
154 |
except Exception as ex: |
155 |
from App import App |
156 |
from AppDocData import MessageType |
157 |
|
158 |
message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
|
159 |
f"{sys.exc_info()[-1].tb_lineno}"
|
160 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
161 |
|
162 |
def make_text_box(self, text_node, position): |
163 |
try:
|
164 |
loc = [self.text_scale[2] + position[0], self.text_scale[3] + position[1]] |
165 |
|
166 |
text = text_node.text |
167 |
angle = 0#round(float(text_node.attrib['Angle']), 2) |
168 |
|
169 |
#_height = self.text_scale[1]
|
170 |
_height = round(float(text_node.attrib['Height']), 2) |
171 |
loc[1] += round(_height / 2, 2) |
172 |
_height = round(_height * self.text_scale[4], 2) |
173 |
loc[1] -= round(_height / 2, 2) |
174 |
|
175 |
#_width = round(_height * len(text) * self.text_scale[4])
|
176 |
_width = round(float(text_node.attrib['Width']), 2) |
177 |
|
178 |
allowed_error = 0.01
|
179 |
if abs(angle - 1.57) < allowed_error: |
180 |
_height, _width = _width, _height |
181 |
loc[0], loc[1] = loc[0] - _width, loc[1] - _height + _width |
182 |
elif abs(angle - 4.71) < allowed_error: |
183 |
_height, _width = _width, _height |
184 |
loc[0], loc[1] = loc[0] - _width, loc[1] + _height - _width |
185 |
|
186 |
rect = QRect(loc[0], loc[1], _width, _height) |
187 |
rect._text = text_node.text |
188 |
|
189 |
return rect
|
190 |
|
191 |
except Exception as ex: |
192 |
message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
|
193 |
f"{sys.exc_info()[-1].tb_lineno}"
|
194 |
|
195 |
print(message) |
196 |
|
197 |
|
198 |
def texts_to_xml(self, rects, symbol_areas): |
199 |
"""try to convert text element to id2 xml"""
|
200 |
import uuid |
201 |
from EngineeringTextItem import QEngineeringTextItem |
202 |
from TextItemFactory import TextItemFactory |
203 |
from TextInfo import TextInfo |
204 |
from CodeTables import CodeTable |
205 |
|
206 |
try:
|
207 |
app_doc_data = AppDocData.instance() |
208 |
factory = TextItemFactory.instance() |
209 |
nodes = [] |
210 |
|
211 |
'''
|
212 |
loc = [self.text_scale[2] + position[0], self.text_scale[3] + position[1]]
|
213 |
|
214 |
text = text_node.text
|
215 |
angle = 0#round(float(text_node.attrib['Angle']), 2)
|
216 |
|
217 |
#_height = self.text_scale[1]
|
218 |
_height = round(float(text_node.attrib['Height']), 2)
|
219 |
loc[1] += round(_height / 2, 2)
|
220 |
_height = round(_height * self.text_scale[4], 2)
|
221 |
loc[1] -= round(_height / 2, 2)
|
222 |
|
223 |
#_width = round(_height * len(text) * self.text_scale[4])
|
224 |
_width = round(float(text_node.attrib['Width']), 2)
|
225 |
|
226 |
allowed_error = 0.01
|
227 |
if abs(angle - 1.57) < allowed_error:
|
228 |
_height, _width = _width, _height
|
229 |
loc[0], loc[1] = loc[0] - _width, loc[1] - _height + _width
|
230 |
elif abs(angle - 4.71) < allowed_error:
|
231 |
_height, _width = _width, _height
|
232 |
loc[0], loc[1] = loc[0] - _width, loc[1] + _height - _width
|
233 |
'''
|
234 |
|
235 |
# merge adjacent text box
|
236 |
configs = app_doc_data.getConfigs('Text Recognition', 'Merge Size') |
237 |
mergeSize = int(configs[0].value) if 1 == len(configs) else 10 |
238 |
gap_size = 3
|
239 |
|
240 |
horizontals = rects |
241 |
|
242 |
h_merges = [] |
243 |
for horizontal1 in horizontals: |
244 |
for horizontal2 in horizontals: |
245 |
if horizontal1 is horizontal2: |
246 |
continue
|
247 |
if abs(horizontal1.center().y() - horizontal2.center().y()) < gap_size: |
248 |
l1, l2 = horizontal1.left() - mergeSize, horizontal2.left() - mergeSize |
249 |
r1, r2 = horizontal1.right() + mergeSize, horizontal2.right() + mergeSize |
250 |
l_x_y, s_x_y = [l1, r1], [l2, r2] |
251 |
if not (max(l_x_y) < min(s_x_y) or max(s_x_y) < min(l_x_y)): |
252 |
inserted = False
|
253 |
for merge in h_merges: |
254 |
if horizontal1 in merge and horizontal2 in merge: |
255 |
inserted = True
|
256 |
break
|
257 |
elif horizontal1 in merge and horizontal2 not in merge: |
258 |
merge.append(horizontal2) |
259 |
inserted = True
|
260 |
break
|
261 |
elif horizontal2 in merge and horizontal1 not in merge: |
262 |
merge.append(horizontal1) |
263 |
inserted = True
|
264 |
break
|
265 |
if not inserted: |
266 |
h_merges.append([horizontal1, horizontal2]) |
267 |
|
268 |
for merge in h_merges: |
269 |
for rect in merge: |
270 |
if rect in rects: |
271 |
rects.remove(rect) |
272 |
else:
|
273 |
pass
|
274 |
|
275 |
for merge in h_merges: |
276 |
max_x, max_y, min_x, min_y = 0, 0, sys.maxsize, sys.maxsize |
277 |
for rect in merge: |
278 |
if rect.left() < min_x:
|
279 |
min_x = rect.left() |
280 |
if rect.right() > max_x:
|
281 |
max_x = rect.right() |
282 |
if rect.top() < min_y:
|
283 |
min_y = rect.top() |
284 |
if rect.bottom() > max_y:
|
285 |
max_y = rect.bottom() |
286 |
|
287 |
text = None
|
288 |
for rect in sorted(merge, key=lambda param:param.left()): |
289 |
if text is None: |
290 |
text = rect._text |
291 |
elif text[-1] != '/' and text[-1] != '-' and text[-1] != '.': |
292 |
text = text + ' ' + rect._text
|
293 |
else:
|
294 |
text = text + rect._text |
295 |
|
296 |
rect = QRect(min_x, min_y, max_x - min_x, max_y - min_y) |
297 |
rect._text = text |
298 |
rects.append(rect) |
299 |
# up to here
|
300 |
|
301 |
for rect in rects: |
302 |
node = None
|
303 |
text = rect._text |
304 |
loc = [rect.left(), rect.top()] |
305 |
_width, _height = rect.width(), rect.height() |
306 |
|
307 |
_item = factory.createTextItem(TextInfo(text, 10, 10, 10, 10, 0)) # just for type check |
308 |
|
309 |
item = QEngineeringTextItem() |
310 |
item.setPlainText(text) |
311 |
item.loc = loc |
312 |
item.size = (_width, _height) |
313 |
item.angle = 0
|
314 |
|
315 |
for area in app_doc_data.getAreaList(): |
316 |
if area.contains([loc[0], loc[1]]): |
317 |
item.area = area.name |
318 |
break
|
319 |
|
320 |
# determine instrument text
|
321 |
if item.area != 'None' and item.area != 'Drawing': |
322 |
node = item.toXml() |
323 |
elif item.area == 'Drawing' and type(_item) is not QEngineeringTextItem: |
324 |
node = item.toXml() |
325 |
elif False:#item.area == 'Drawing': |
326 |
app_doc_data.getReplaceTables() |
327 |
app_doc_data.getCustomTables() |
328 |
app_doc_data.loadSymbolAttributeCodeTables() |
329 |
for table in CodeTable.TABLES.values(): |
330 |
if table.find_match_exactly(item.text()):
|
331 |
node = item.toXml() |
332 |
break
|
333 |
|
334 |
if node == None: |
335 |
for area in symbol_areas: |
336 |
if area.contains([loc[0], loc[1]]): |
337 |
node = item.toXml() |
338 |
break
|
339 |
else: # allow all text item |
340 |
node = item.toXml() |
341 |
|
342 |
if node:
|
343 |
nodes.append(node) |
344 |
|
345 |
return nodes
|
346 |
except Exception as ex: |
347 |
message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
|
348 |
f"{sys.exc_info()[-1].tb_lineno}"
|
349 |
|
350 |
print(message) |
351 |
|
352 |
def close(self): |
353 |
QDialog.reject(self)
|